代码都来自 React 18 源码, 大家可以放心食用
读完收获
当我们 React.creataContext() 时 发生了什么?
Context.Provider 是如何挂载与更新的?
useContext 是怎么样获取到上下文对象的?
从下面的代码开始入手分析,下面代码完成的是点击文字后随机修改文字颜色。
import * as React from "react";
import { createContext, useState, useContext } from "react";
import ReactDOM from "react-dom";
const genColor = () => `hsla(${Math.random() * 360}, 70%, 50%)`;
type MyContext = { color: string, changer: (any) => void };
const ThemeContext = createContext < MyContext > null;
const Comp = () => {
const { color, changer } = useContext(ThemeContext);
return (
<button style={{ color, fontSize: "2em" }} onClick={changer}>
Hello World! Click to change color
</button>
);
};
const App = () => {
const [state, setState] = useState({ color: "green" });
const { color } = state;
const changer = () => setState({ color: genColor() });
return (
<ThemeContext.Provider value={{ color, changer }}>
<Comp />
</ThemeContext.Provider>
);
};
Mount 阶段
第一步:首先通过 createContext 创建了 context 对象。我们传入的 defaultValue 会被当作 context 对象的 __currentValue 属性,同时 我们也可以发现 context.Provider 是一个 React 元素。 Provider 的 __context 属性指向了 context 对象。
export function createContext<T>(defaultValue: T): ReactContext<T> {
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
_currentValue: defaultValue,
_currentValue2: defaultValue,
_threadCount: 0,
Provider: (null: any),
Consumer: (null: any),
_defaultValue: (null: any),
_globalName: (null: any),
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
context.Consumer = context;
return context;
}
第二步:这里我们默认 reconciler 阶段 深度优先调度遍历到了 App Fiber 节点,创建了颜色状态,改变颜色状态的函数。传入 Context.Provider 组件的 props 当中。
const [state, setState] = useState({ color: "green" });
const { color } = state;
const changer = () => setState({ color: genColor() });
return (
<ThemeContext.Provider value={{ color, changer }}>
<Comp />
</ThemeContext.Provider>
);
第三步:默认走到 reconciler 阶段, beginWork 函数中开始挂载 Context.Provider,调用updateContextProvider 函数
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
switch (workInProgress.tag) {
case ContextProvider:
return updateContextProvider(current, workInProgress, renderLanes);
}
}
第四步:updateContextProvider 函数当中调用 pushProvider 函数。
function updateContextProvider(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
) {
/* 这里的 workInProgress.type 就是 Provider.type = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
}; */
const providerType: ReactProviderType<any> = workInProgress.type;
// providerType._context; 就是 Provider.type.context
const context: ReactContext<any> = providerType._context;
const newProps = workInProgress.pendingProps;
const oldProps = workInProgress.memoizedProps;
const newValue = newProps.value;
pushProvider(workInProgress, context, newValue);
const newChildren = newProps.children;
reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}
let hasWarnedAboutUsingContextAsConsumer = false;
第五步: pushProvider 函数当中将 newValue 也就是 <ThemeContext.Provider value={{ color, changer }}> 的 props 值 赋值给 context.__currentValue。现在 context.__currentValue = { color, changer }
export function pushProvider<T>(
providerFiber: Fiber,
context: ReactContext<T>,
nextValue: T
): void {
context._currentValue = nextValue;
context._currentValue2 = nextValue;
}
第六步:默认 reconciler 阶段深度优先调度遍历到了 Comp Fiber 节点, beginWork 函数判断 workInProgress.tag根据 tag 进行挂载,函数式组件调用 renderWithHooks,在 renderWithHooks 函数当中 调用 Comp 函数。继续调用 useContext(ThemeContext)
const Comp = () => {
const { color, changer } = useContext(ThemeContext);
return (
<button style={{ color, fontSize: "2em" }} onClick={changer}>
Hello World! Click to change color
</button>
);
};
第七步:调用 useContext 并传入 ThemeContext,返回 context.__currentVale, 由于第五步挂载 <Context.Provider/> 的时候,已经将 context.__currntValue 修改成了 {color:"green", changer: () => setState(randomColor)},所以该对象被返回。
function useContext<T>(context: ReactContext<T>): T {
return context._currentValue;
}
第八步:Comp 返回新的虚拟 Dom,去 reconcilChildren…… reconciler 阶段结束 -> render 阶段完成之后,真实 Dom 挂载,文字颜色就是上下文对象提供的 color。
return (
<button style={{ color, fontSize: "2em" }} onClick={changer}>
Hello World! Click to change color
</button>
);
自此 Context 挂载阶段完成。
Update 阶段
第一步:点击按钮,出发 changer函数,changer 函数则调用了 setState。
const App = () => {
const [state, setState] = useState({ color: "green" });
const { color } = state;
const changer = () => setState({ color: genColor() });
return (
<ThemeContext.Provider value={{ color, changer }}>
<Comp />
</ThemeContext.Provider>
);
};
第二步:(这里不展开,setState的细节)我们 默认已经又来到了 reconciler 阶段的 App Fiber 节点,又开始调用App函数,调用 useState -> updateState, 得到了 新的颜色状态。传入 Context.Provider 组件的 props 当中。
const [state, setState] = useState({ color: "green" });
const { color } = state;
const changer = () => setState({ color: genColor() });
return (
<ThemeContext.Provider value={{ color, changer }}>
<Comp />
</ThemeContext.Provider>
);
第三步: reconciler 阶段, beginWork 函数中开始挂载 Context.Provider 组件调用updateContextProvider 函数
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
switch (workInProgress.tag) {
case ContextProvider:
return updateContextProvider(current, workInProgress, renderLanes);
}
}
第四步:updateContextProvider 函数当中调用 pushProvider 函数。
function updateContextProvider(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
) {
/* 这里的 workInProgress.type 就是 Provider.type = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
}; */
const providerType: ReactProviderType<any> = workInProgress.type;
// providerType._context; 就是 Provider.type.context
const context: ReactContext<any> = providerType._context;
const newProps = workInProgress.pendingProps;
const oldProps = workInProgress.memoizedProps;
const newValue = newProps.value;
pushProvider(workInProgress, context, newValue);
const newChildren = newProps.children;
reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}
let hasWarnedAboutUsingContextAsConsumer = false;
第五步: pushProvider 函数当中将 newValue 也就是 <ThemeContext.Provider value={{ color, changer }}> 的 props 值 赋值给 context.__currentValue。现在 context.__currentValue = { color, changer }
export function pushProvider<T>(
providerFiber: Fiber,
context: ReactContext<T>,
nextValue: T
): void {
context._currentValue = nextValue;
context._currentValue2 = nextValue;
}
第六步:默认 reconciler 阶段深度优先调度遍历到了 Comp Fiber 节点, beginWork 函数判断 workInProgress.tag根据 tag 进行挂载,函数式组件调用 renderWithHooks,在 renderWithHooks 函数当中 调用 Comp 函数。继续调用 useContext(ThemeContext)
const Comp = () => {
const { color, changer } = useContext(ThemeContext);
return (
<button style={{ color, fontSize: "2em" }} onClick={changer}>
Hello World! Click to change color
</button>
);
};
第七步:调用 useContext 并传入 ThemeContext,返回 context.__currentVale, 由于第四步挂载 <Context.Provider/> 的时候,已经将 context.__currntValue 修改成了 {color:"新颜色", changer: () => setState(randomColor)},所以该对象被返回。
function useContext<T>(context: ReactContext<T>): T {
return context._currentValue;
}
第八步:Comp 返回新的虚拟 Dom,去 reconcilChildren…… reconciler 阶段结束 -> render 阶段完成之后,真实 Dom 更新,文字颜色就是上下文对象提供的更新后的 color。
return (
<button style={{ color, fontSize: "2em" }} onClick={changer}>
Hello World! Click to change color
</button>
);
自此 Context 更新阶段完成。