跳到主要内容

认识

2023年06月10日
柏拉文
越努力,越幸运

一、认识


useContext 是一个 React Hook,可以让你读取和订阅组件中的 context

二、执行函数组件


React.jsbeginWork 阶段, 遇到 FunctionComponent 类型的 Fiber, 会执行 updateFunctionComponentupdateFunctionComponent 内部调用 renderWithHooks。主要工作如下:

  1. 重置状态: 函数组件执行前, 重置当前正在处理的 hook、当前正在使用的 hook

  2. 记录当前组件 fiber: 通过 currentlyRenderingFiber 变量记录当前组件 fiber

  3. 选择对应阶段的 Hook: mount 阶段选择 HooksDispatcherOnMount 作为 Hooks Map, update 阶段选择 HooksDispatcherOnUpdate 作为 Hooks Map函数组件mount 阶段 创建 hook 数据结构, 存储到 fiber.memoizedState 或者 hook.next, 初次建立其 hooksfiber 之间的关系。函数组件 update 阶段 找到当前函数组件 fiber, 找到当前 hook, 更新 hook 状态。

  4. 执行函数组件: 执行我们真正函数组件,所有的 hooks 将依次执行, 每个 hook 内部要根据 currentlyRenderingFiber 读取对应的内容,

  5. 重置状态: 执行完 Component, 立马重置 currentlyRenderingFiber, 防止函数组件外部调用。置当前正在处理的 hook、当前正在使用的 hook

2.1 /packages/react-reconciler/fiberHooks.js

export function renderWithHooks(workInProgress, Component, lane) {
renderLane = lane;
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;

const current = workInProgress.alternate;

if (current !== null) {
// update
currentDispatcher.current = HooksDispatcherOnUpdate;
} else {
// mount
currentDispatcher.current = HooksDispatcherOnMount;
}

const props = workInProgress.pendingProps;
const children = Component(props);

currentHook = null;
renderLane = NoLane;
workInProgressHook = null;
currentlyRenderingFiber = null;

return children;
}

2.2 /packages/react-reconciler/fiberHooks.js

/**
* @description: Mount 阶段 Hooks 实现
*/
const HooksDispatcherOnMount = {
use,
useRef: mountRef,
useMemo: mountMemo,
useState: mountState,
useEffect: mountEffect,
useContext: readContext,
useCallback: mountCallback,
useTransition: mountTransition
};

/**
* @description: Update 阶段 Hooks 实现
*/
const HooksDispatcherOnUpdate = {
use,
useRef: updateRef,
useMemo: updateMemo,
useState: updateState,
useEffect: updateEffect,
useContext: readContext,
useCallback: updateCallback,
useTransition: updateTransition
};

三、函数组件 mount 阶段 Hook


mount 阶段 useContext 实现逻辑如下:

/**
* @description: Mount 阶段 Hooks 实现
*/
const HooksDispatcherOnMount = {
useRef: mountRef,
useState: mountState,
useEffect: mountEffect,
useContext: readContext,
useTransition: mountTransition
};
function readContext(context) {
const consumer = currentlyRenderingFiber;
if (consumer == null) {
throw new Error('请在函数组件内调用 useContext');
}
const value = context._currentValue;
return value;
}

四、函数组件 update 阶段 Hook


update 阶段 useContext 实现逻辑如下:

/**
* @description: Mount 阶段 Hooks 实现
*/
const HooksDispatcherOnMount = {
useRef: mountRef,
useState: mountState,
useEffect: mountEffect,
useContext: readContext,
useTransition: mountTransition
};
function readContext(context) {
const consumer = currentlyRenderingFiber;
if (consumer == null) {
throw new Error('请在函数组件内调用 useContext');
}
const value = context._currentValue;
return value;
}

五、沉淀与思考


5.1 useContext 为什么没有其他 Hooks 的限制?

useContext 的实现逻辑是从 workInProgress.type._context 取得上下文逻辑, 没有从 currentlyRenderingFiber 中使用 Hook 数据, 所以也没有其他 Hook 的使用限制, 比如 useContext 可以在 if 语句中使用。其他 Hook 需要从 currentlyRenderingFiber 中的单向链表中取 Hook 数据, 必须在函数组件顶层使用, 保证 Hook 单向链表与当前 Hook 数据一一对应。