react 版本:v17.0.3
Hook 是 React 16.8 的新增特性,它可以让我们在不编写 class组件 的情况下使用 state 以及其它的 React 特性。
1、hook 入口
首先我们在入口文件 packages/react/index.js 文件中找到对外导出的 hook:
// packages/react/index.js
export {
// ...
useCallback,
useContext,
useDebugValue,
useDeferredValue,
useEffect,
useImperativeHandle,
useLayoutEffect,
useMemo,
useMutableSource,
useReducer,
useRef,
useState,
// ...
} from './src/React';
可以看到,它们在 packages/react/src/React.js 导入了 ReactHooks文件的 hooks,然后对外导出:
// packages/react/src/React.js
// 从 ReactHooks 文件中导入 hooks
import {
// ...
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useMutableSource,
useReducer,
useRef,
useState,
// ...
} from './ReactHooks';
// 对外导出 hooks
export {
// ...
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useMutableSource,
useReducer,
useRef,
useState,
// ...
};
在 packages/react/src/React.js 文件中,我们可以看到 hooks 都是导自 packages/react/src/ReactHooks.js 文件:
// packages/react/src/ReactHooks.js
export function useContext<T>(Context: ReactContext<T>): T {
const dispatcher = resolveDispatcher();
// 删除了 Dev 部分的代码
return dispatcher.useContext(Context);
}
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
export function useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg, init);
}
export function useRef<T>(initialValue: T): {|current: T|} {
const dispatcher = resolveDispatcher();
return dispatcher.useRef(initialValue);
}
export function useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, deps);
}
export function useLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const dispatcher = resolveDispatcher();
return dispatcher.useLayoutEffect(create, deps);
}
export function useCallback<T>(
callback: T,
deps: Array<mixed> | void | null,
): T {
const dispatcher = resolveDispatcher();
return dispatcher.useCallback(callback, deps);
}
export function useMemo<T>(
create: () => T,
deps: Array<mixed> | void | null,
): T {
const dispatcher = resolveDispatcher();
return dispatcher.useMemo(create, deps);
}
export function useImperativeHandle<T>(
ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
const dispatcher = resolveDispatcher();
return dispatcher.useImperativeHandle(ref, create, deps);
}
export function useDebugValue<T>(
value: T,
formatterFn: ?(value: T) => mixed,
): void {
if (__DEV__) {
const dispatcher = resolveDispatcher();
return dispatcher.useDebugValue(value, formatterFn);
}
}
export function useTransition(): [boolean, (() => void) => void] {
const dispatcher = resolveDispatcher();
return dispatcher.useTransition();
}
export function useDeferredValue<T>(value: T): T {
const dispatcher = resolveDispatcher();
return dispatcher.useDeferredValue(value);
}
export function useOpaqueIdentifier(): OpaqueIDType | void {
const dispatcher = resolveDispatcher();
return dispatcher.useOpaqueIdentifier();
}
export function useMutableSource<Source, Snapshot>(
source: MutableSource<Source>,
getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
): Snapshot {
const dispatcher = resolveDispatcher();
return dispatcher.useMutableSource(source, getSnapshot, subscribe);
}
export function useCacheRefresh(): <T>(?() => T, ?T) => void {
const dispatcher = resolveDispatcher();
// $FlowFixMe This is unstable, thus optional
return dispatcher.useCacheRefresh();
}
我们仔细观察每个hook函数,会发现每个hook函数都调用了 resolveDispatcher()函数,这个函数返回的是 ReactCurrentDispatcher.current:
// packages/react/src/ReactHooks.js
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
// 删除了 __DEV__ 部分的代码
return ((dispatcher: any): Dispatcher);
}
那么 ReactCurrentDispatcher 又是什么呢?我们在 packages/react/src/ReactCurrentDispatcher.js 文件中看看它的定义:
// packages/react/src/ReactCurrentDispatcher.js
import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
const ReactCurrentDispatcher = {
current: (null: null | Dispatcher),
};
export default ReactCurrentDispatcher;
可以看到,ReactCurrentDispatcher 仅仅是一个只有 current 属性的对象。
通过 ReactCurrentDispatcher.current 关键词全局查找,最终在 packages/react-reconciler/src/ReactFiberHooks.new.js 文件中找到了对 ReactCurrentDispatcher.current 的赋值。
// packages/react-reconciler/src/ReactFiberHooks.new.js
// 代码有删减,仅贴出关键性代码
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
if (__DEV__) {
// 删除了 Dev 部分的代码
} else {
// 对应的生命周期对象挂载到 ReactCurrentDispatcher.current 上
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
// 组件挂载时 hook 的初始化
? HooksDispatcherOnMount
// 组件更新时 hook 的初始化
: HooksDispatcherOnUpdate;
}
let children = Component(props, secondArg);
// 检查在渲染阶段是否更新
if (didScheduleRenderPhaseUpdateDuringThisPass) {
do {
ReactCurrentDispatcher.current = __DEV__
// 开发环境
? HooksDispatcherOnRerenderInDEV
// 生产环境
: HooksDispatcherOnRerender;
children = Component(props, secondArg);
} while (didScheduleRenderPhaseUpdateDuringThisPass);
}
// 错误捕获处理
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
return children;
}
-
在组件挂载时,将 ReactCurrentDispatcher.current 赋值为 HooksDispatcherOnMount 对象。
-
在组件更新时,将 ReactCurrentDispatcher.current 赋值为 HooksDispatcherOnUpdate。
-
在render阶段,将 ReactCurrentDispatcher.current 赋值为 HooksDispatcherOnRerender 对象。
我们来看看这三个对象:
// packages/react-reconciler/src/ReactFiberHooks.new.js
// 组件挂载阶段赋值给 ReactCurrentDispatcher.current 的 HooksDispatcherOnMount 对象
const HooksDispatcherOnMount: Dispatcher = {
readContext,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useDeferredValue: mountDeferredValue,
useTransition: mountTransition,
useMutableSource: mountMutableSource,
useOpaqueIdentifier: mountOpaqueIdentifier,
unstable_isNewReconciler: enableNewReconciler,
};
// 组件更新阶段赋值给 ReactCurrentDispatcher.current 的 HooksDispatcherOnUpdate 对象
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useDeferredValue: updateDeferredValue,
useTransition: updateTransition,
useMutableSource: updateMutableSource,
useOpaqueIdentifier: updateOpaqueIdentifier,
unstable_isNewReconciler: enableNewReconciler,
};
// 组件render阶段赋值给 ReactCurrentDispatcher.current 的 HooksDispatcherOnRerender 对象
const HooksDispatcherOnRerender: Dispatcher = {
readContext,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: rerenderReducer,
useRef: updateRef,
useState: rerenderState,
useDebugValue: updateDebugValue,
useDeferredValue: rerenderDeferredValue,
useTransition: rerenderTransition,
useMutableSource: updateMutableSource,
useOpaqueIdentifier: rerenderOpaqueIdentifier,
unstable_isNewReconciler: enableNewReconciler,
};
可以看到,HooksDispatcherOnMount、HooksDispatcherOnUpdate、HooksDispatcherOnRerender 这三个对象分别挂载了组件在 Mount 阶段、Update 阶段、Render 阶段对应的 Hook 处理函数。因此,ReactCurrentDispatcher.current 上挂载的是组件在相应阶段的 Hook 处理函数,这些处理函数是在 renderWithHooks 函数中挂载到 ReactCurrentDispatcher.current 上。
2、入口函数 renderWithHooks
React Fiber 会从 packages/react-reconciler/src/ReactFiberBeginWork.new.js 文件中的 beginWork() 开始执行,对于 Function Component,调用了 updateFunctionComponent() 加载或更新组件:
// packages/react-reconciler/src/ReactFiberBeginWork.new.js
// 函数组件
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
// 解析组件的 props
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
// 更新函数组件
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
我们再来看看 updateFunctionComponent 函数:
// packages/react-reconciler/src/ReactFiberBeginWork.new.js
function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderLanes,
) {
// 删除了Dev部分的代码
let context;
// 删除了部分代码
let nextChildren;
// 删除了部分代码
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
setIsRendering(true);
// 调用 renderWithHooks 获取函数组件的 children
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
if (
debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictLegacyMode
) {
disableLogs();
try {
// 调用 renderWithHooks 获取函数组件的 children
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
} finally {
reenableLogs();
}
}
setIsRendering(false);
} else {
// 调用 renderWithHooks 获取函数组件的 children
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
}
// 删除了部分代码
return workInProgress.child;
}
可以看到,在 updateFunctionComponent 中,调用 renderWithHooks 获取函数组件的 children,然后赋值给 nextChildren,最后在 reconcileChildren() 中将 nextChildren 挂载到了 workInProgress 的 child 属性上。这里我们先不关心 reconcileChildren 是如何处理节点的,我们回到 renderWithHooks 的执行流程。
至此,我们知道了 React Hooks 的渲染核心入口是 renderWithHooks,其被调用的流程可总结如下:
现在,我们重点来看看 renderWithHooks 具体做了什么。
// packages/react-reconciler/src/ReactFiberHooks.new.js
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
renderLanes = nextRenderLanes;
// 将当前的workInProgressFiber节点存在全局变量currentlyRenderingFiber中,这样方便后面获取Fiber信息
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
// The following should have already been reset
// 如果current的值为空,说明还没有hook对象被挂载
// 而根据hook对象结构可知,current.memoizedState指向下一个current
// 给 ReactCurrentDispatcher.current 赋值
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
// 初始化时
? HooksDispatcherOnMount
// 更新时
: HooksDispatcherOnUpdate;
let children = Component(props, secondArg);
// Check if there was a render phase update
// 检查在渲染阶段是否更新
if (didScheduleRenderPhaseUpdateDuringThisPass) {
// Keep rendering in a loop for as long as render phase updates continue to
// be scheduled. Use a counter to prevent infinite loops.
let numberOfReRenders: number = 0;
do {
didScheduleRenderPhaseUpdateDuringThisPass = false;
numberOfReRenders += 1;
// Start over from the beginning of the list
currentHook = null;
workInProgressHook = null;
workInProgress.updateQueue = null;
ReactCurrentDispatcher.current = __DEV__
? HooksDispatcherOnRerenderInDEV
: HooksDispatcherOnRerender;
children = Component(props, secondArg);
} while (didScheduleRenderPhaseUpdateDuringThisPass);
}
// 重置
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
// 相关属性的初始化
const didRenderTooFewHooks =
currentHook !== null && currentHook.next !== null;
renderLanes = NoLanes;
currentlyRenderingFiber = (null: any);
currentHook = null;
workInProgressHook = null;
didScheduleRenderPhaseUpdate = false;
if (enableLazyContextPropagation) {
if (current !== null) {
if (!checkIfWorkInProgressReceivedUpdate()) {
const currentDependencies = current.dependencies;
if (
currentDependencies !== null &&
checkIfContextChanged(currentDependencies)
) {
markWorkInProgressReceivedUpdate();
}
}
}
}
return children;
}
renderWithHooks 为Function Component 的入口函数,它主要包括三部分,首先是给 ReactCurrentDispatcher.current 赋值,然后是didScheduleRenderPhaseUpdateDuringThisPass (检查在渲染阶段是否有更新) 以及一些属性的初始化工作。
我们重点来看 ReactCurrentDispatcher.current 的赋值部分:
// 如果current的值为空,说明还没有hook对象被挂载
// 而根据hook对象结构可知,current.memoizedState指向下一个current
// 给 ReactCurrentDispatcher.current 赋值
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
// 初始化时
? HooksDispatcherOnMount
// 更新时
: HooksDispatcherOnUpdate;
注意:下面,我们以 useMemo 为例讲解 hook 的挂载。
如果 current (即当前的Fiber) 为空,说明还没有 hook 对象被挂载,说明是首次加载,将 ReactCurrentDispatcher.current.useMemo 赋值成 HooksDispatcherOnMount.useMemo:
// packages/react-reconciler/src/ReactFiberHooks.new.js
const HooksDispatcherOnMount: Dispatcher = {
// ...
useMemo: mountMemo,
// ...
};
如果current (即当前的Fiber) 不为空,说明是在组件更新阶段,ReactCurrentDispatcher.current.useMemo 赋值成 HooksDispatcherOnUpdate.useMemo:
// packages/react-reconciler/src/ReactFiberHooks.new.js
const HooksDispatcherOnUpdate: Dispatcher = {
// ...
useMemo: updateMemo,
// ...
};
因此,我们可以得到这样的等式:
-
首次加载时:
useMemo = ReactCurrentDispatcher.current.useMemo = HooksDispatcherOnMount.useMemo = mountMemo;
-
更新时:
useMemo = ReactCurrentDispatcher.current.useMemo = HooksDispatcherOnUpdate.useMemo = updateMemo;
为了更直观地看出 useMemo 在首次加载和更新时的处理逻辑,我们将上面的等式转换成流程图,如下:
由上图,我们可以直观地看出,组件在首次加载时,执行 useMemo,其实执行的是 mountMemo,而组件更新时,则执行的是updateMemo 。
其它 hook 同理,不再赘述。
3、总结
在文中,我们介绍了 Hooks 的入口以及入口函数 renderWithHooks。
在 packages/react-reconciler/src/ReactFiberHooks.new.js 文件中,有所有 React Hooks 的核心实现。并在 renderWithHooks 函数中把 Hooks 在 Mount 阶段、Update 阶段、Render 阶段对应的 Hook 处理函数挂载到了 ReactCurrentDispatcher.current 属性上。
当我们调用某个 hook 时,实际上调用的是挂载在 ReactCurrentDispatcher.current 属性上的对应的 hook 处理函数。