最近在调研状态管理库,顺便研究了下Context(React 18.1)的原理,
以下解析基于该case
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| const dataContext = createContext(); const themeContext = createContext(); const Provider = ({ children }) => { const [data, setData] = useState("This is the data!"); const dataContextValue = useMemo(() => { return [data, setData]; }, [data, setData]);
return ( <themeContext.Provider value="dark"> <dataContext.Provider value={dataContextValue}> {children} </dataContext.Provider> </themeContext.Provider> ); };
const Consumer = () => { const [data, setData] = useContext(dataContext); const theme = useContext(themeContext); return ( <React.Fragment> <div>current theme is {theme}</div> <button onClick={() => setData(Date.now())}>Click to change data!</button> <div>{data}</div> </React.Fragment> ); };
const App = () => { return ( <Provider> <Consumer /> </Provider> ); };
|
context在fiber上的结构
case中的Consumer组件使用了两个Context,context的存储也和别的hooks一样,是链表结构存储在fiber.dependency上。

在Context value更新之后,是如何通知订阅该Context的组件更新的
context value的更新一般是通过Provider所在组件通过setState触发,setState后整个React树从上而下进行reconciliation,在update Provider组件时发现value变更了,然后通知订阅方进行更新。

如何通知相关联的组件
dfs遍历fiber tree,判断每个组件上的dependency链表包含了当前变更的context,发现有变更,将该fiber的lane与当前的更新优先级renderLane合并,后续在reconcile该组件时就会判断fiber上是否包含renderLane来决定该fiber是否需要更新

判断是否有update

多个相同 Provider 可以嵌套使用,里层的会覆盖外层的数据的实现原理
React通过堆栈来实现,在reconcile Provider时会将最新的value赋值到context上,将旧值存储到站栈里,在完成对Provider的reconcilation(completeWork)后,将旧值pop出来作为context.value。
1 2 3 4 5 6 7 8 9 10 11 12 13
| function pushProvider(providerFiber, context, nextValue) { push(valueCursor, context._currentValue, providerFiber); context._currentValue = nextValue; } function popProvider(context, providerFiber) { var currentValue = valueCursor.current; pop(valueCursor, providerFiber); if ( currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) { context._currentValue = context._defaultValue; } else { context._currentValue = currentValue; } }
|
全局状态的合理使用
因为context value的变更会遍历所有的fiber,如果页面很复杂,组件层级很深数量庞大,开销也是很大的。
所以在给Provider设置value时应该用useMemo(value)以及减少value的变化。
React Redux 是如何设计 Provider 的?
React Redux 的 Provider 接收 store,而 store 在创建初期就保持不变,因此 Provider 的 store 在整个应用生命周期内都不会发生改变,也就不会触发订阅的组件重新渲染。
我们通过 dispatch 触发的状态变更,实际上改变的是 store.state,然后通过 useSelector 或者 connect 订阅。