React Context 原理

最近在调研状态管理库,顺便研究了下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-chain

在Context value更新之后,是如何通知订阅该Context的组件更新的

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

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

判断是否有update
update-component

多个相同 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 订阅。