實現一個簡單的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) 須要翻

路漫漫其修遠兮,吾將上下而求索,前端工程師永遠處在學習的路上

相關文章
相關標籤/搜索