useState 作为 React 最常用的基础 Hook,它具有对号入座存储组件数据的神奇能力,在没有传递额外键值的情况下,它是如何做到的?

1
const [state, setState] = useState(initialState);

查看 React 源码ReactHooks.js

1
2
3
4
5
6
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}

进一步可以看到 useState 的依赖链条

useState -> resolveDispatcher -> ReactCurrentDispatcher

查看对 ReactCurrentDispatcher.current 的赋值,可以看到 ReactFiberHooks.jsrenderWithHooks 方法逻辑,可以看到 current.memoizedState 是使用挂载或是更新响应的关键,而 currentrenderWithHooks 的传入参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// renderWithHooks 片段
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;
…………
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// HooksDispatcherOnMount 片段
const HooksDispatcherOnMount: Dispatcher = {
…………
useState: mountState,
…………
};

// HooksDispatcherOnUpdate 片段
const HooksDispatcherOnUpdate: Dispatcher = {
…………
useState: updateState,
…………
};

function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
// $FlowFixMe: Flow doesn't like mixed types
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
};
hook.queue = queue;
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}

function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}

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;
}

字面意思上看,组件初始化时,useState 的调用将会调用到 mountState 方法,组件重新渲染时则会调用 updateState 方法。

初始化时,会创建一个 hook 对象,将其挂载到 Hooks 链表中,而后将自身标记为 workInProgressHookhook.memoizedStatehook.queue.dispatch 分别对应 state 的值和 setter,该 setter 还绑定了 currentlyRenderingFiber

这里可以看出关键点在 Fiber 的 Hooks 链表,只要调用顺序符合既定规则,在渲染时就可以做到相关存储对号入座。

因此这也可以解释为什么 React Hook 规则 要求

  1. 只在最顶层使用 Hook(不要在循环,条件或嵌套函数中调用 Hook)
  2. 只在 React 函数中调用 Hook(不要在普通的 JavaScript 函数中调用 Hook)