一、前言
React在很早就支持了context,但是在官方文档中却不推荐我们使用它。
The vast majority of applications do not need to use context.
If you want your application to be stable, don’t use context. It is an experimental API and it is likely to break in future releases of React.
绝大多数应用程序不需要使用 context。
如果你想让你的应用更稳定,不要使用context。因为这是一个实验性的API,在未来的React版本中可能会被更改。
但是,在React16.3版本时,官方更新了新版context并且兼容了旧版context。
二、旧版Context API
- 父组件定义context
//父组件A
class A extends React.Component {
getChildContext() {
return {color: "red"};
}
}
A.childContextTypes = {
color: PropTypes.string
};
- 子组件使用context
//子组件B
class B extends React.Component {
render() {
return (
<p>
{this.context.color}
</p>
);
}
}
B.contextTypes = {
color: PropTypes.string
};
- 旧版 Context API 虽然使用起来不算复杂,但当和
shouldComponentUpdate
搭配使用时就很容易出问题。
//例如以下组件
<A>
<B>
<C />
</B>
</A>
按照正常流程,父组件 A 通过 getChildContext
设置 Context,子组件 C 通过 this.context
读取 Context。
当组件 A 要更新 Context 时:
- 组件 A 通过
setState
设置新的 Context 值同时触发子组件重新render。 - 组件 B 重新render。
- 组件 C 重新render,并拿到更新后的Context。
正常流程没毛病。
但是,当我们在组件B中定义shouldComponentUpdate
时:
- 组件 A 通过
setState
设置新的 Context 值同时触发子组件重新render。 - 组件 B 执行
shouldComponetUpdate
,由于组件 B 自身并不依赖 Context,所以shouldComponetUpdate
检测到 state 与 prop 均未变化因此返回false
。无需重新render。 - 由于 B 组件没有重新render。这导致组件 C 也不会重新render,因此也就无法获取到最新的 Context 值。
三、新版Context API
新版Context 新增了
creactContext()
方法用来创建一个context对象。这个对象包含两个组件,一个是Provider
(生产者),另一个是Consumer
(消费者)。
-
Provider
和Consumer
必须来自同一个Context对象,即一个由React.createContext()
创建的Context对象。 -
React.createContext()
方法接收一个参数做默认值,当Consumer
外层没有对应的Provider
时就会使用该默认值。 -
Provider
组件使用Object.is
方法判断prop值是否发生改变,当prop的值改变时,其内部组件树中对应的Consumer
组件会接收到新值并重新渲染。此过程不受shouldComponentUpdete
方法的影响。 -
Consumer
组件接收一个函数作为children
prop 并利用该函数的返回值生成组件树的模式被称为 Render Props 模式。
四、新版Context API用法
-
首先创建一个context对象
//定义一个颜色的默认值 const defaultColor = "#000"; const ColorContext = React.createContext(defaultColor);
-
使用Provider组件生成一个context
//父组件A class A extends React.Component { state = { childColor: "#000" } render(){ return ( <ColorContext.Provider value={this.state.childColor}> <B></B> </ColorContext.Provider> ) } }
-
使用Consumer组件获取context对象的值
//子组件B class B extends React.Component { render(){ return <C></C> } } //子组件C class C extends React.Component { render(){ return ( <ColorContext.Consumer> { value => <span style={{color: value}}>context颜色</span> } </ColorContext.Consumer> ) } }
五、吐槽新版Context
-
新的Context API使用render props这种模式优雅的解决了旧版Context的问题,但是也引来了新的问题——如何管理context对象。
// 主题Context, 默认light const ThemeContext = React.createContext('light'); // 登陆用户Context const UserContext = React.createContext(); // 一个依赖于两个Context的中间组件 function Toolbar(props) { return ( <ThemeContext.Consumer> {theme => ( <UserContext.Consumer> {user => ( <ProfilePage user={user} theme={theme} /> )} </UserContext.Consumer> )} </ThemeContext.Consumer> ); } class App extends React.Component { render() { const {signedInUser, theme} = this.props; // App组件提供Context的初始值 return ( <ThemeContext.Provider value={theme}> <UserContext.Provider value={signedInUser}> <Toolbar /> </UserContext.Provider> </ThemeContext.Provider> ); } }
-
在组件生命周期中访问Context
class Button extends React.Component { componentDidMount() { // ThemeContext value is this.props.theme } componentDidUpdate(prevProps, prevState) { // Previous 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> );