在 React 中数据总是单向传递的,如果某个属性许多组件都需要就会使该过程变得极其繁琐,Context 提供了在组件间共享此类数据的方式,而不必显式地通过组件树的逐层传递 props。
什么情况下使用 Context
-
当某个(些)属性需要传递的层级很深时
考虑下面的例子:
class App extends Component { state = { count: 1 } render() { return ( <div className='App'> <h1>这是根组件</h1> <Father count={this.state.count} /> </div> ); } } class Father extends Component { render() { return ( <div className='father'> <h1>这是父组件</h1> <Son count={this.props.count} /> </div> ); } } class Son extends Component { render() { return ( <div className='son'> <h1>这是子组件</h1> <h2>{this.props.count}</h2> </div> ); } }
如果
Son
组件需要来自根组件的 count,则需要从App
组件开始通过props 属性自上而下传递给Son
组件。使用 context, 我们可以避免通过中间元素传递 props:// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。 // 为当前的 theme 创建一个 context(1为默认值)。 const countContext = React.createContext(1); class App extends Component { state = { count: 1 }; render() { return ( // 使用一个 Provider 来将当前的 state 传递给以下的组件树。 // 无论多深,任何组件都能读取这个值。 // 在这个例子中,我们将 state 作为当前的值传递下去。 <Provider value={this.state}> <div className='App'> <h1>这是根组件</h1> <Father /> </div> </Provider> ); } } // 中间的组件再也不必指明往下传递了。 class Father extends Component { render() { return ( <div className='father'> <h1>这是父组件</h1> <Son /> </div> ); } } class Son extends Component { // 指定 contextType 读取当前的 count context。 // React 会往上找到最近的 count Provider,然后使用它的值。 // 在这个例子中,当前的 count 值为 1。 static contextType = countContext; render() { return ( <div className='son'> <h1>这是子组件</h1> <h2>{this.context.count}</h2> </div> ); } // 也可使用 Consumer 渲染出 context /* render() { const { Consumer } = globalContext; return ( <div className='son'> <h1>这是子组件</h1> <h2>{this.context.count}</h2> <Consumer>{(context) => <h2>{context.count}</h2>}</Consumer> <button onClick={this.add}>点我加1</button> <button onClick={this.minus}>点我减1</button> </div> ); } */ }
如何在
Son
组件中更改 Context 呢?// 导出 context 修改函数 const actions = (self) => ({ add() { self.setState((preState) => ({ count: preState.count + 1 })); }, minus() { self.setState((preState) => ({ count: preState.count - 1 })); }, }); class App extends Component { // 扩展 actions 并传入 this state = { count: InitialContext.count, ...actions(this) }; render() { return ( <Provider value={this.state}> <div className='App'> <h1>这是根组件</h1> <Father /> </div> </Provider> ); } } class Son extends Component { static contextType = globalContext; // 调用在 context 中定义好的加减函数 add = () => { this.context.add(); }; minus = () => { this.context.minus(); }; render() { return ( <div className='son'> <h1>这是子组件</h1> <h2>{this.context.count}</h2> <button onClick={this.add}>点我加1</button> <button onClick={this.minus}>点我减1</button> </div> ); } }
Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。如果仅仅是为了避免层层传递属性可以使用组件组合。
-
无亲属关系的组件需要共用的数据
考虑下面的例子:
class App extends Component { render() { return ( <div className='App'> <h1>这是根组件</h1> </div> ); } } class Test extends Component { render() { return ( <div className='son'> <h1>这是另一个组件</h1> </div> ); } }
App
组件和Test
组件既不是父子关系也不是兄弟关系,如果想要将App
组件中的数据传递给Test
组件可以利用 Context 来实现:const countContext = React.createContext("我是要传递的数据"); class App extends Component { render() { return ( <div className='App'> <h1>这是根组件</h1> </div> ); } } class Test extends Component { static contextType = globalContext; render() { return ( <div className='test'> <h1>这是另一个组件</h1> <h2>{this.context}</h2> </div> ); } }
注意事项
Context 会使用参考标识(reference identity)来决定何时进行渲染,这里可能会有一些陷阱,当 Provider
的父组件进行重渲染时,可能会在 Consumers
组件中触发意外的渲染。举个例子,当每一次 Provider
重渲染时,以下的代码会重渲染所有下面的 Consumers
组件,因为 value
属性总是被赋值为新的对象:
class App extends Component {
render() {
return (
<Provider value={1}>
<div className='App'>
<h1>这是根组件</h1>
<Father />
</div>
</Provider>
);
}
}
因此,将 value
状态提升到父节点的 state
中是更好的做法