useState 作为 React 最常用的基础 Hook,它具有对号入座存储组件数据的神奇能力,在没有传递额外键值的情况下,它是如何做到的?
1 const [state , set State] = 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.js 中 renderWithHooks
方法逻辑,可以看到 current.memoizedState
是使用挂载或是更新响应的关键,而 current
是 renderWithHooks
的传入参数
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 const HooksDispatcherOnMount: Dispatcher = { ………… useState: mountState, ………… }; const HooksDispatcherOnUpdate: Dispatcher = { ………… useState: updateState, ………… }; function mountState <S >( initialState: (() => S) | S, ): [S , Dispatch <BasicStateAction <S >>] { const hook = mountWorkInProgressHook(); if (typeof initialState === 'function' ) { 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 ) { currentlyRenderingFiber.memoizedState = workInProgressHook = hook; } else { workInProgressHook = workInProgressHook.next = hook; } return workInProgressHook; }
字面意思上看,组件初始化时,useState
的调用将会调用到 mountState
方法,组件重新渲染时则会调用 updateState
方法。
初始化时,会创建一个 hook
对象,将其挂载到 Hooks 链表中,而后将自身标记为 workInProgressHook
,hook.memoizedState
和 hook.queue.dispatch
分别对应 state 的值和 setter,该 setter 还绑定了 currentlyRenderingFiber
这里可以看出关键点在 Fiber 的 Hooks 链表 ,只要调用顺序符合既定规则,在渲染时就可以做到相关存储对号入座。
因此这也可以解释为什么 React Hook 规则 要求
只在最顶层使用 Hook(不要在循环,条件或嵌套函数中调用 Hook)
只在 React 函数中调用 Hook(不要在普通的 JavaScript 函数中调用 Hook)