Description
React16 更新了新的 Context API,在这之前官方一直都不被官方提倡使用。
Context API
截至 Reaact 16.6.3,共提供了四组 api: React.createContext
、ReactContext.Provider
、Class.contextType
、ReactContext.Consumer
。
下面我们把提供 context 的组件叫为 provider(提供者),把用到 context 组件叫做 consum(消费者)
React.createContext(defaultValue)
该方法传入一个初始值/默认值,创建一个 ReactContext
。
// 基本的data
const themes = {
light: {
color: '#000',
background: '#eee',
},
dark: {
color: '#fff',
background: '#222',
},
};
// 创建 React context 赋默认值
const ReactContext = React.createContext({
theme: themes.dark,
toggleTheme: () => {},
});
console.log(ReactContext);
返回的ReactContext
包含了我们后面要用到的Consumer
和Provider
。
这里的Consumer
其实指向的就是ReactContext
,而Provider.context
也指向了ReactContext
,方便后面值的传递与获取。
细心的同学可能发现这里有两个 value 值,_currentValue
和_currentValue2
,后面会讲到这块。
ReactContext.Provider
Provider 顾名思义既 context 的提供者,我们可以给这个组件传一个value
值来覆盖createContext
传入的默认值,当value
值变化时就会通知到子级的消费者。
所以都是要配合 ReactContext.Consumer
或 Class.contextType
使用。
一般我们传的时候,不会直接传一个对象。value={{name: 'jsonz'}}
,因为这样每次render的时候都会认为是全新的 Object。
React内部是根据 Object.is的polyfill 来判断是否value是否有被更新
<ReactContext.Provider value={this.state}>
<Wrap />
</ReactContext.Provider>
当 provider 的 value 变化时,会把当前 provider 的 value 赋值给 ReactContext._currentValue
,后面我们的 consum 可以直接从_currentValue
去获取最新的值
Class.contextType
class Child extends React.Component {
componentDidMount() {
console.log(this.context);
}
render() {
return <div>{this.context.theme.color}</div>;
}
// static contextType = ReactContext
}
Child.contextType = ReactContext;
我们可以通过把 Class.ContextType
指向 ReactContext
(也可以用static
属性),然后在类的生命周期函数或者 render 函数里面通过 this.context
去获取 ReactContext 值。
但是这种方式有个弊端,就是一个类的 contextType 属性只能指向一个 ReactContext。如果想要同时有多个消费者,就要用到下一小节的 React.Consumer
React 在执行updateClassInstance
的时候,会判断该的class
有没有contextType
这个属性,如果contextType
不为空,则返回ReactContext._currentValue
,这样我们组件就能拿到最新的 contextValue 了。
当然里面还有很多细节,比如调用生命周期函数ComponentWillReceiveProps
之前会加多一个oldContext !== nextContext
的判断等等。
有兴趣的可以根据我之前的系列,自己看源码 乐趣更多~
ReactContext.Consumer
ReactContext.Consumer
其实是以组件的形式 consum(消费)ReactContext 的另一种方式。
比起 Class.contextType
最大的不同就是可以同时消费多个 ReactContext,而且他的子级只允许是一个 Function!
const ThemeContext = React.createContext('dark');
const UserContext = React.createContext({ name: 'jsonz' });
function Demo() {
return (
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<div>
{user.name} = {theme}
</div>
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
ReactContext.Consumer
在收到需要更新的时候,会去拿组件自身的 currentValue 作为最新的 contextValue,再拿 props.children
当 render 方法,所以我们前面说该组件的子级只能是一个 Function。
此时就算子级返回的是另一个ReactContext.Consumer
,那也只是按照刚才的逻辑再走一遍。
综合使用的demo github仓库
新Context API
新的Context API其实依赖 React.CreateContext
生成的组件来维护最新的 currentValue,所以不存在被 shouldComponentUpdate
阻断子级 context 更新的问题。
大概的原理是
-
当执行
workLoop
中对fiberTree进行更新时,如果发现ReactContext.Provider
组件的值发生更新(变更)的时候,都会去广播。然后找到子级中对应的消费者consum
,把他和父级的渲染优先级改为最高优先级(第二步会用到)。 -
当执行到某个
classComponent
时,如果这个组件是不需要更新的 (新旧 props、state一致或者shouldComponentUpdate返回了false) ,这时候会去看他子级的childExpirationTime
优先级是否足够高,如果足够高就无视当前的 shouldUpdate,把子级返回到 workLoop里面进行下一次的更新。
这张流程图只是拎了一部分关于context更新的来讲,要了解React整个运行的机制可以看之前的几篇
结语
Context API的更新,最直观的进步就是通过组件解决了旧Context被中间组件shouldComponentUpdate
阻断的问题,在一定程度上可以代替小部分的Redux使用场景。目前个人的一个小项目就没有引用redux,而是直接在 contentComponet 统一用 ContextAPI 去管理。
至于性能问题 emmm 不知道用 chrome react devtool Profiler
为什么好像没测出有多大的区别...