概述
React 在 v16.8 提供了 Hook 特性,React Hooks 通过增强函数式组件,为 Function Component 注入一些功能,例如 useState 让原本的 Stateless Function Component 有了状态。
工作原理
接下来我们从 useState 这个 hook 为切入点,打开 React Hooks 源码看看背后的原理。
先看个 Demo:
import React, { useState } from 'react';function App() {const [count, dispatchCount] = useState(0);return (<div><span>{count}</span><button onClick={() => dispatchCount(count + 1)}>increment</button></div>)}
上述 Demo 使用函数组件定义了一个计数器,相对于普通函数组件,该组件提供了 count 的状态,每点击按钮一次 count 就加 1。
接下来我们看下 useState 的源码,它是如何保存 count 状态、更新 count 状态的。
状态保存与更新
去除无关代码之后,可以看到我们调用的 useState 只是一个入口,最终是调用 dispatcher 的一个方法。并且在 React.js 中只负责定义。
// react/src/ReactHooks.jsconst ReactCurrentDispatcher = {/*** @internal* @type {ReactComponent}*/current: (null: null | Dispatcher),};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;}
Hook 只有在 FunctionalComponent 更新的时候才会被调用,在 updateFunctionComponent 的方法中找到了 Hook 更新的入口 renderWithHooks,在 renderWithHooks 中依照条件对 ReactCurrentDispatcher.current 进行了赋值。
// react-reconciler/src/ReactFiberHooks.jsfunction renderWithHooks(current: Fiber | null,workInProgress: Fiber,Component: any,props: any,secondArg: any,nextRenderExpirationTime: ExpirationTime,) {// ... 省略无关代码ReactCurrentDispatcher.current =current === null || current.memoizedState === null// Mount? HooksDispatcherOnMount// Update: HooksDispatcherOnUpdate;// ...}
可以看到 Dispatcher 分为 Mount 和 Update。这里我们可以找到对应的 mountState 和 updateState,其他 hook 也是这么分类的,如果我们要查看其他 hook 代码,均可以在这里找到对应时机的代码。
状态保存
在了解具体代码之前,我们先了解下 Hook 的定义.
Hook = {// 当前 hook 的 state,就是上述 Demo 中的 count 值memoizedState: any,// 多次调用,保存队列queue: UpdateQueue<any, any> | null,// 下一个 hook,通过该属性连接成一个 hook 的单向链表next: Hook | null,|};
对于一个组件内的 hook 对象,会被保存在 App 组件对应的 Fiber 对象的 memoizedState 中。保存结构大致如下:
fiber = {memoizedState: {memoizedState: initialState,queue: {},next: {memoizedState: initialState,queue: {},next: null,},}}
状态更新
当首次渲染时,调用 mountState 时,返回 [hook.memoizedState, dispatch] 。
// react-reconciler/src/ReactFiberHooks.js// 获取 hook 对象function mountWorkInProgressHook(): Hook {const hook: Hook = {memoizedState: null,baseState: null,baseQueue: null,queue: null,next: null,};if (workInProgressHook === null) {// 将 hook 加到 fiber.memoizedStatecurrentlyRenderingFiber.memoizedState = workInProgressHook = hook;} else {// 插入链表,指定下一个 hookworkInProgressHook = workInProgressHook.next = hook;}return workInProgressHook;}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 = (hook.queue = {pending: null,dispatch: null,lastRenderedReducer: basicStateReducer,lastRenderedState: (initialState: any),});const dispatch: Dispatch<BasicStateAction<S>,> = (queue.dispatch = (dispatchAction.bind(null,currentlyRenderingFiber,queue,): any));return [hook.memoizedState, dispatch];}
调用上述 Demo 中 dispatchCount,其实就是调用 dispatchAction.bind(null, currentlyRenderingFiber, queue),
function dispatchAction<S, A>(fiber: Fiber,queue: UpdateQueue<S, A>,action: A,) {// 每调用一次 dispatchCount,都会创建一个 update 对象,记录要更新的值 actionconst update: Update<S, A> = {action,next: null,// ...};// ...// 将更新附加到列表的末尾const pending = queue.pending;if (pending === null) {// 这是第一次更新,创建一个循环列表。update.next = update;} else {// 插入新的 update 节点update.next = pending.next;pending.next = update;}queue.pending = update;// ...// 更新渲染调度scheduleWork()}
更新 state
当调用 dispatchCount 时,这时候实际是调用 updateState 对 state 进行合并处理,在 updateReducer 中会遍历 hook 链表,得到最新 memoizedState 并返回。
// react-reconciler/src/ReactFiberHooks.jsfunction updateState<S>(initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {return updateReducer(basicStateReducer, (initialState: any));}function updateReducer<S, I, A>(reducer: (S, A) => S,initialArg: I,init?: I => S,) {const hook = updateWorkInProgressHook();const queue = hook.queue;// ...let first = baseQueue.next;do {// 获取传入的 state actionconst action = update.action;// 更新 statenewState = reducer(newState, action);// 遍历下一个更新 actionupdate = update.next;} while (update !== null && update !== first)hook.memoizedState = newState;const dispatch: Dispatch<A> = (queue.dispatch: any);return [hook.memoizedState, dispatch];}
小结
对于 useState 的逻辑,就相当于原有的 class 组件的 state,只是在函数组件中,他的状态存在 Fiber 节点上。通过链表操作遍历更新该 Fiber 节点下的 hook 对象来更新函数组件中的 state。
