最近研究了一些框架原理層的東西,以前用過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) 須要翻
路漫漫其修遠兮,吾將上下而求索,前端工程師永遠處在學習的路上