前言
在官網(wǎng)中的Hook 規(guī)則里,講到了使用 Hook 需要遵循的兩條規(guī)則:
- 不要在循環(huán)毫痕,條件或嵌套函數(shù)中調(diào)用 Hook典格。
- 只在 React 函數(shù)中調(diào)用 Hook试疙。
接下來從源碼看看到底為什么棋凳。
從 ReactHooks 的源碼里可以看到 useState, useEffect笙隙,useRef刃唤,useMemo隔心,useCallback...... 所有的 Hooks 里的代碼都大同小異,都是調(diào)用 resolveDispatcher 尚胞,下面以 useState 為例硬霍。
為何不要在循環(huán),條件或嵌套函數(shù)中調(diào)用 Hook笼裳?
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return ((dispatcher: any): Dispatcher);
}
可以看到 hooks 都是從 ReactCurrentDispatcher 上的 current 來的唯卖,初始化為null。(Dispatcher 里聲明的躬柬,正好是一些 hooks 的類型)
const ReactCurrentDispatcher = {
/**
* @internal
* @type {ReactComponent}
*/
current: (null: null | Dispatcher),
};
問題:ReactCurrentDispatcher 上的 current 是在哪里被賦值的呢耐床?
在 github 上搜索 ReactCurrentDispatcher 可以發(fā)現(xiàn)是在 ReactFiberHooks.new.js 文件里的 renderWithHooks 賦值的。(從名字上就可以感覺到這個(gè)和 fiber 架構(gòu)和 hooks 有關(guān)了)
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
//......
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
//......
}
可以看到初始化和更新的 hooks 是兩套 api楔脯。
我們先只看mount 階段的 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,
};
useState 執(zhí)行的自然就是 mountState。
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
//......
return [hook.memoizedState, dispatch];
}
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
可以看到 mountWorkInProgressHook 里的代碼胯甩,每次生成一個(gè) hook 對(duì)象(里面保存了當(dāng)前 hook 信息)昧廷,然后通過 next 指向下一個(gè) hook堪嫂,以鏈表形式串聯(lián)起來。
總結(jié):hooks 的關(guān)系是通過 next 指向進(jìn)行關(guān)聯(lián)的木柬,所以如果寫在循環(huán)或條件語句中的話皆串,next 的順序就亂了,沒有辦法按照正確的順序執(zhí)行了眉枕。
為何只能在 React 函數(shù)中調(diào)用 Hook恶复?
從上面我們已經(jīng)可以知道,hooks 是在 renderWithHooks 內(nèi)賦值的速挑。
問題:renderWithHooks 又是在哪里被 import 使用了呢谤牡?
我們再來搜一下 renderWithHooks,可以看到是在 ReactFiberBeginWork.new.js 文件內(nèi)被使用的 姥宝。(從文件的命名上翅萤,我們可以知道,這個(gè)是 fiber 開始執(zhí)行的 js 文件)
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
//......
switch (workInProgress.tag) {
//......
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
//......
}
//......
從這一段代碼里我們可以知道腊满,函數(shù)組件和 class 組件 調(diào)用的是不同的方法套么,而 updateFunctionComponent 里調(diào)用了 renderWithHooks 函數(shù),updateClassComponent 沒有調(diào)用碳蛋。所以關(guān)于為什么的答案已經(jīng)出來了胚泌。
總結(jié)一下:hooks 需要從 ReactCurrentDispatcher.current 上取得,而其賦值是在執(zhí)行 renderWithHooks 時(shí)賦值的肃弟。執(zhí)行函數(shù)組件的 updateFunctionComponent 內(nèi)有調(diào)用 renderWithHooks 函數(shù)玷室,執(zhí)行 class 組件的 updateClassComponent 內(nèi)沒有調(diào)用其方法,所以使用 hooks 時(shí)必須寫在函數(shù)組件內(nèi)愕乎。
最后
希望這是加深對(duì) hook 原理了解的第一步阵苇。