认识
一、认识
useContext
是一个 React Hook
,可以让你读取和订阅组件中的 context
。
二、执行函数组件
在 React.js
的 beginWork
阶段, 遇到 FunctionComponent
类型的 Fiber
, 会执行 updateFunctionComponent
。updateFunctionComponent
内部调用 renderWithHooks
。主要工作如下:
-
重置状态: 函数组件执行前, 重置当前正在处理的
hook
、当前正在使用的hook
-
记录当前组件
fiber
: 通过currentlyRenderingFiber
变量记录当前组件fiber
-
选择对应阶段的
Hook
:mount
阶段选择HooksDispatcherOnMount
作为Hooks Map
,update
阶段选择HooksDispatcherOnUpdate
作为Hooks Map
。 函数组件mount
阶段 创建hook
数据结构, 存储到fiber.memoizedState
或者hook.next
, 初次建立其hooks
与fiber
之间的关系。函数组件update
阶段 找到当前函数组件fiber
, 找到当前hook
, 更新hook
状态。 -
执行函数组件: 执行我们真正函数组件,所有的
hooks
将依次执行, 每个hook
内部要根据currentlyRenderingFiber
读取对应的内容, -
重置状态: 执行完
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
数据一一对应。