最近研究了一些框架原理层的东西,以前用过react16版本新的context api之后感受比以前的好用不少,同时要想研究redux-react或者mobx-react原理就必需要搞懂新旧context的原理 ,因此通过一点研究,这里实现一个简版的react context。html
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
function Toolbar(props) {
return (
<>
<ThemedButton />
<ThemedColor />
</>
);
}
// 第一种拿到context方式,仅限类组件
class ThemedButton extends React.Component {
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
// 第二种拿到context方式,children的返回值能够是函数组件
function ThemedColor(){
return (
<ThemeContext.Consumer>
{
value=>(
<div>
{value}
<div/>
)
}
</ThemeContext.Consumer>
)
}
复制代码
上面是帮你们温习一下用法,下面准备实现思路,核心是属性值的传递能够用类组件的静态属性来作,一个类组件可能有不少实例,可是静态属性不会变的,全部实例均可以取到。前端
经过createContext
方法导出一个对象包含两个高阶组件:Provider和Consumer。每次更新时,Provider组都会走静态的getDerivedStateFromProps方法,这时把props中的value挂载给静态属性上,作到提供的值实时变化,而Consumer与Provider共享一块空间,因此每次render都会取Provider上的静态属性value,这样就实现了两个组件的联结。第二种获取context的方式就是让Consumer的children是一个函数,传参给要返回的组件达到更新的目的react
function createContext(defaultValue) {
class Provider extends React.Component {
static value = defaultValue;
constructor(props) {
super(props);
Provider.value = props.value;
}
static getDerivedStateFromProps(nextProps, prevState) {
Provider.value = nextProps.value;
return prevState;
}
render() {
return this.props.children;
}
}
class Consumer extends React.Component {
render() {
return this.props.children(Provider.value);
}
}
return { Provider, Consumer }
}
复制代码
static contextType = MyContext;
, 类把createContext方法生成的Context对象直接放在静态属性上,那么咱们每次在render的时候提早把context里面的值赋给this.context 模拟实现以下👇
class MyClass extends React.Component {
static contextType = MyContext;
render() {
this.context = MyClass.contextType.Provider.value;/* 这一行是模拟实现 */
let value = this.context;
return <> {value} </> } } 复制代码
以上的只能跑起基本的demo,还有不少问题没有解决redux
实际上,不管在刚才的demo和 老版Context
Legacy Context都没法实现这个功能,这也是老版的context为人诟病的其中一点,那新版Context有什么黑魔法能够实现这个呢?api
答案是context变化会引起又一次check数组
下面稍有些晦涩,涉及到一些Fiber的运做机制: 得益于Fiber的链表机制,一个Provider的全部的consumer里面的children都会注册到事件池里面,并提供一个propagateContextChange方法。 Provider变化触发updateContextProvider函数,从而每一个consumer的child的Fiber节点会执行propagateContextChange方法(若是位运算经过的话,能够看第二个问题), 在 propagateContextChange 中,以当前 fiber 节点为根的子树中寻找相匹配 Consumer 节点,给与更新标记。前端工程师
所以,虽然 shouldComponentUpdate 形成了 Consumer的父组件没法被标记更新,但 Provider 的 propagateContextChange 能使 Consumer 组件从新被标记,从而可以被 render。框架
// 源码在此,请注意createContext接收了第二个参数
export function createContext<T>( defaultValue: T, calculateChangedBits: ?(a: T, b: T) => number, ): ReactContext<T> {
if (calculateChangedBits === undefined) {
calculateChangedBits = null;
}
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE, // 这个类型标识很特殊,第一个问题与此有关
_calculateChangedBits: calculateChangedBits,
_currentValue: defaultValue,
_currentValue2: defaultValue,
_threadCount: 0,
Provider: (null: any),
Consumer: (null: any),
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
context.Consumer = context;
return context;
}
复制代码
其实第二个参数是个函数,用来判断比较context是否更新,咱们能够用它来自定义更新细粒度从而避免没必要要的更新。ide
这个东西涉及到一些位运算,不展开讲了,后面有时间可能会把这段补充上去。函数
calculateChangedBits
observedBits
这两个api是一对,如今这个东西可能不太稳定,慎用
ObservedBits: React Context的秘密功能
React tips — Context API (performance considerations) 须要翻
路漫漫其修远兮,吾将上下而求索,前端工程师永远处在学习的路上