实现一个简单的React16新版context

最近研究了一些框架原理层的东西,以前用过react16版本新的context api之后感受比以前的好用不少,同时要想研究redux-react或者mobx-react原理就必需要搞懂新旧context的原理 ,因此通过一点研究,这里实现一个简版的react context。html

新版context api文档 官方示例以下👇

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>
   )
}
复制代码

基本思路

上面是帮你们温习一下用法,下面准备实现思路,核心是属性值的传递能够用类组件的静态属性来作,一个类组件可能有不少实例,可是静态属性不会变的,全部实例均可以取到。前端

实现provider和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 }
}
复制代码

静态属性获取context值的方式实现

咱们能够观察到上面的实现 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

1.provider和consumer之间若是有n个嵌套层级,中间有一级shouldcomponentupdate为false,处于底层的consumer仍然会接到更新,实际源码怎么实现?

实际上,不管在刚才的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。框架

2.createContext接受的第二个参数用来作什么?

// 源码在此,请注意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是一对,如今这个东西可能不太稳定,慎用

一些参考文章

不同的 React context

ObservedBits: React Context的秘密功能

React tips — Context API (performance considerations) 须要翻

路漫漫其修远兮,吾将上下而求索,前端工程师永远处在学习的路上

相关文章
相关标签/搜索