IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    使用 Context 避免深层属性传递

    冷石发表于 2022-08-29 09:01:22
    love 0

    最近在项目发现了一些看起来很神奇的代码,一个组件 A 的方法通过 props 传到组件 B,然后到组件 C,再到组件 D,再到组件 E,最后到组件 D, 简直是千层饼😂。

    前言

    最近在项目发现了一些看起来很神奇的代码,一个组件 A 的方法通过 props 传到组件 B,然后到组件 C,再到组件 D,再到组件 E,最后到组件 D, 简直是千层饼😂。

    Xnip2021-07-19_20-03-31.jpg

    提问一番后知道了这种将属性深层传递的现象叫 Prop Drilling,本文说明如何使用 React 的 Context API 避免这种现象。

    什么是 Context

    Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

    在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但此种用法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。

    Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。这些组件共享的以及变化不频繁的数据正是 Context 用武之地。

    如何使用 Context

    举个例子🌰 说明
    react-context-hooks.vercel.app_(iPhone 6_7_8).png
    在线示例
    本文代码

    创建 Context

    首先用 React.createContext 方法创建一个 AuthContext,表示当前应用的认证状态,创建时可以传入默认值。

    1
    2
    3
    4
    5
    6
    // AuthContext.js

    export const AuthContext = React.createContext({
    user: null,
    isAuthenticated: false,
    })

    然后创建 AuthContextProvider 组件,在组件中初始化状态,定义改变状态的方法,将这些数据和方法传递给 Context.Provider 的 value 属性 ,value 可以接收数据和方法,最后在组件中渲染子组件 children 。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // AuthContext.js

    export class AuthContextProvider extends Component {
    state = {
    user: null,
    isAuthenticated: false,
    }

    login = (user) => {
    // ...
    }

    logout = () => {
    // ...
    }

    render() {
    return (
    <AuthContext.Provider value={(this.state, this.login, this.logout)}>
    {this.props.children}
    </AuthContext.Provider>
    )
    }
    }

    使用 Context

    有多种方式获得 Context 中的数据

    1. 使用 Context.Comsumer
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // AppHeader.js

    <AuthContext.Consumer>
    {(authContext) => (
    <header>
    // ...
    </header>
    )}
    </AuthContext.Consumer>
    1. 使用组件的 ContextType 属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // AppFooterButtons.js

    class AppFooterButtons extends Component {
    static contextType = AuthContext

    render() {
    return (
    <div>
    <button onClick={this.login}>Login</button>
    <button onClick={this.context.logout}>Logout</button>
    </div>
    )
    }
    }
    1. 使用 useContext

    对于函数式组件可以使用 useContext 方法获取状态数据,可以同时获取多个状态。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // AppBody.js

    const AppBoy = () => {
    const authContext = useContext(AuthContext)
    const { languages, current, changeLanguage } = useContext(LanguageContext)

    return (
    <div>
    <h1>
    Hello, {authContext.isAuthenticated ? authContext.user.name : 'World'}
    </h1>
    </div>
    )
    }

    修改 Context

    1. 对于 class 组件,使用 setState 方法更新组件的 state。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // AuthContext.js 

    export class AuthContextProvider extends Component {
    // ...

    login = (user) => {
    if (this.state.isAuthenticated) {
    return
    }
    this.setState({
    user,
    isAuthenticated: true,
    })
    }

    logout = () => {
    this.setState({
    user: null,
    isAuthenticated: false,
    })
    }

    // ...
    }
    1. 函数式组件使用 useState
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // LanguageContext.js

    export const LanguageContextProvider = (props) => {
    // ...

    const [current, setCurrent] = useState(0)
    const changeLanguage = (val) => {
    setCurrent(val)
    }

    // ...
    }

    最终使用 Context 将应用中通用状态统一管理,无需再层层传递属性。

    结论

    本文说明什么是 Context,以及使用 Context 的多种方式。使用 Context 可以有效的避免 Prop Drilling 现象,将需要深层传递的属性和方法提取出来,达到共用的目的。应用 Context 的最佳时机是应用的一些全局性,不会频繁变化的数据。

    注意事项

    使用 Context 之前的考虑

    Context 主要应用场景在于_很多_不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。

    如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。

    参考

    Avoid Prop Drilling with React Context
    Prop Drilling
    Context



沪ICP备19023445号-2号
友情链接