Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性.
Context 的使用场景:
Context 设计目的是为共享那些被认为对于一个组件树而言是 "全局"的数据.例如当前ti认证的用户,主题或或首选语言.
function ThemedButton(props) {
return <Button theme={props.theme} />;
}
//中间组件
function Toolbar(props) {
//Toolbar 组件必须添加一个额外的 theme 属性
// 然后传递他给 themedButton 组件
return (
<div>
<ThemedButton theme={props.theme} />
</div>
);
}
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
使用 context , 可以避免通过中间元素传递 props:
//创建一个 theme Context , 默认 theme 的值为 light
const ThemeContext = React.createContext('light');
function ThemedButton (props) {
//ThemedButton 组件从 context 接收 theme
return (
<ThemeContext.Consumer>
{ theme => <Button { ...props } theme={theme} /> }
<ThemeContext.Consumer>
);
}
//中间组件
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
)
}
}
注意: 你要仅仅为了避免在几个层级下的组件传递 props 而使用 context . 他是被用于在多个层级的多个组件需要访问相同数据的情景.
API
React.createContext:
const { Provider, Consumer } = React.createContext(defaultValue);
创建一对 { Provider, Consumer }. 当 React 渲染context 组件 Consumer 时,他将从组件树的上层中最接近的匹配的 Provider 读取当前的 context 值.
如果上层的组件树没有一个匹配的 Provider , 而此时你需要渲染一个 Consumer 组件,那么你可以用到 defaultValue. 这有助于在不封装他们的情况下对组件进行测试.
Provider
<Provider value={ /* some value */ }>
react 组件允许 Consumers 订阅context 的改变
接收一个value 属性传递给 Provider 的后代 Consumers. 一个 Provider 可以联系到多个 Consumers. Providers 可以被嵌套以覆盖组件树内更深层次的值.
Consumer
<Consumer>
{ value => /* render something based on the context value */ }
</Consumer>
一个可以订阅context 变化的 React 组件.
接收一个函数作为子节点.函数接收当前context 的值并返回一个 React 节点. 传递给函数的value 将等于组件树中上层 context 的最近的 Provider的 value属性. 如果context 没有Provider .那么value 参数将等于被传递给 createContext() 的 defaultValue.
每当 Provider 的值发送改变时, 作为 Provider 后代的所有 Consumers 都会重新渲染. 从 Provider 到其后代的Consumers 传播不受 shouldComponentUpdate方法约束,因此即使祖先组件退出更新时,后代Consumer 也会被更新.
在生命周期方法中访问 Context
在生命周期方法中从上下文访问值是一种相对常见的用例.而不是将上下文添加到每一个生命周期方法中,只需要将它作为一个 props 传递,然后像通常使用 props 一样去使用它:
class Button extends React.Component {
componentDidMount() {
// ThemeContext value is this.props.theme
}
componentDidUpdate(prevProps,prevState) {
//Prevous ThemeContext value is prevProps.theme
//New ThemeContext value is this.props.theme
}
render() {
const { theme , children } = this.props;
return (
<button className={ theme ? 'dark' : 'light' }>
{ children }
</button>
)
}
}
export default props => (
<ThemeContext.Consumer>
{ theme => <Button {...props} theme={theme}> /}
</ThemeContext.Consumer>
)
告诫:
因为 context 使用 reference indentity 确定何时重新渲染, 在 Consumer 中, 当一个 Provider 的父节点重新渲染的时候,有一些问题可能触发意外的渲染.例如下面的代码,所有的 Consumer 在 Provider 重新渲染时,每次都将重新渲染,因为一个新的对象总是被创建对应 Provider 里的 value .
class App extends React.Component {
render () {
return (
<Provider value={{ something: 'something' }}>
<Toolbar />
</Provider>
)
}
}
为了防止这样,提升value 到父节点的state 里.
class App extends React.Component {
constructor (props) {
this.state = {
value: { something: 'something' },
};
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
)
}
}