# 阅读图解React笔记9
Table of Contents
本文仅仅是阅读 图解 React 原理系列 的笔记,了解更多内容请查看原文链接。
状态 Hook
原理概念
前面介绍了 Hook 原理(概览),主要内容有:
- function 类型的 fiber 节点,它的处理函数是 updateFunctionComponent,其中再通过 renderWithHooks 调用 function。
- 在 function 中,通过 Hook Api 创建 Hook 对象。
- 状态 Hook 实现了持久化
- 副作用 Hook 则实现了维护 fiber.flags,并提供了副作用回调
- 多个 Hook 对象构成了一个链表结构,并挂载到 fiber.memoizedState 之上。
- fiber 树更新阶段,把 current.memoizedState 链表上的所有 Hook 按照顺序克隆到 workInProgress.memoizedState 上,实现数据的持久化。
创建 Hook
本节将深入分析状态 Hook 的特性和实现原理。
在 fiber 初次构造阶段,useState 对应的源码 mountState,useReducer 对应的源码 mountReducer
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];}function mountReducer<S, I, A>(reducer: (S, A) => S, initialArg: I, init?: (I) => S) { const hook = mountWorkInProgressHook(); let initialState; if (init !== undefined) { initialState = init(initialArg); } else { initialState = ((initialArg: any): S); }
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = { pending: null, dispatch: null, lastRenderedReducer: reducer, lastRenderedState: (initialState: any), });
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind( null, currentlyRenderingFiber, queue ): any));
return [hook.memoizedState, dispatch];}mountState 和 mountReducer 逻辑简单,主要负责创建 hook,初始化 hook 的属性,最后返回 [当前状态,dispatch 函数]
唯一的不同点是 hook.queue.lastRenderedReducer:
- mountState 使用的内置的 basicStateReducer
- mountReducer 使用的外部传入自定义 reducer
可见 mountState 是 mountReducer 的一种特殊情况,即 useState 也是 useReducer 一种特殊情况。
在 useState(initialState) 函数内部,设置 hook.memoizedState = hook.baseState = initialState; 初始状态被同时保存到了 hook.baseState, hook.memoizedState 中。
- hook.memoizedState: 当前状态
- hook.baseState:基础状态,作为合并 hook.baseQueue 的初始值。
最后返回 [hook.memoizedState, dispatch],所以在 function 中使用的是 hook.memoizedState。
状态更新
有如下示例代码:
import { useState } from 'react'
export default function App() { const [count, dispatch] = useState(0) return ( <button onClick={() => { dispatch(1) }} > {count} </button> )}点击 button,通过 dispatch 函数的更新,dispatch 实际就是 dispatchAction:
function dispatchAction<S, A>(fiber: Fiber, queue: UpdateQueue<S, A>, action: A) { const eventTime = requestEventTime(); const lane = requestUpdateLane(fiber);
const update: Udpate<S, A> = { lane, action, eagerReducer: null, eagerState: null, next: (null, any), };
const pending = queue.pending; if (pending === null) { update.next = update; } else { update.next = pending.next; pending.next = update; } queue.pending = update;
const alternate = fiber.alternate; if ( fiber === currentlyRenderingFiber || (alternate !== null && alternate === currentlyRenderingFiber) ) { didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true; } else { // 省略性能优化部分。。。
scheduleUpdateOnFiber(fiber, lane, eventTime); }}- 创建 update 对象,其中 update.lane 代表优先级。
- 将 update 对象添加到 hook.queue.pending 环形链表。
- 环形链表的特征:为了方便添加新元素和快速拿到队首元素(O(l)),所以 pending 指针指向了链表中最后一个元素。
- 发起调度更新:调用 scheduleUpdateOnFiber,进入 reconciler 运作流程中的输入阶段。
从调用 scheduleUpdateOnFiber 开始,进入了 react-reconciler 包,在 fiber 树构造过程中,再次调用 function,这时 useState 对应的函数是 updateState,实际上调用的 updateReducer。
function 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): [S, Dispatch<A>] { const hook = updateWorkInProgressHook(); const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const current: Hook = (currentHook: any);
let baseQueue = current.baseQueue;
const pendingQueue = queue.pending;
if (pendingQueue !== null) { if (baseQueue !== null) { const baseFirst = baseQueue.next; const pendingFirst = pendingQueue.next; baseQueue.next = pendingFirst; pendingQueue.next = baseFirst; } current.baseQueue = baseQueue = pendingQueue; queue.pending = null; } current.baseQueue = baseQueue = pendingQueue; queue.pending = null;
if (baseQueue !== null) { const first = baseQueue.next; let newState = current.baseState;
let newBaseState = null; let newBaseQueueFirst = null; let newBaseQueueLast = null; let update = first;
do { const updateLane = update.lane; if (!isSubsetOfLanes(renderLanes, updateLane)) { const clone: Update<S, A> = { lane: updateLane, action: update.action, eagerReducer: update.eagerReducer, eagerState: update.eagerState, next: (null: any), }; if (newBaseQueueLast === null) { newBaseQueueFirst = newBaseQueueLast = clone; newBaseState = newState; } else { newBaseQueueLast = newBaseQueueLast.next = clone; }
currentlyRenderingFiber.lanes = mergeLanes(currentlyRenderingFiber.lanes, updateLane); markSkippedUpdateLanes(updateLane); } else { if (newBaseQueueLast !== null) { const clone: Update<S, A> = { lane: NoLane, action: update.action, eagerReducer: update.eagerReducer, eagerState: update.eagerState, next: (null: any), }; nextBaseQueueLast = newBaseQueueLast.next = clone; } if (update.eagerReducer === reducer) { newState = ((update.eagerState: any): S); } else { const action = update.action; newState = reducer(newState, action); } } udpate = update.next; } while (update !== null && update !== first);
if (newBaseState === null) { newBaseState = newState; } else { newBaseQueueLast.next = (newBaseQueueFirst: any); }
if (!is(newState, hook.memoizedState)) { markWorkInProgressReceivedUpdate(); }
hook.memoizedState = newState; hook.baseState = newBaseState; hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState; }
const dispatch: Dispatch<A> = (queue.dispatch: any); return [hook.memoizedState, dispatch];}- 调用 updateWorkInProgressHook 获取 workInProgress 对象。
- 链表拼接:将 hook.queue.pending 拼接到 current.baseQueue。
- 状态计算
- update 优先级不够:加入 baseQueue,等待下一次 render。
- update 优先级足够:状态合并。
- 更新属性。
性能优化
dispatchAction 函数中,在调用 scheduleUpdateOnFiber 之前,针对 update 对象做了性能优化。
- queue.pending 只包含当前 update 时,即当前 update 是 queue.pending 中的第一个 update。
- 直接调用 queue.lastRenderedReducer,计算出 update 之后的 state,记为 eagerState。
- 如果 eagerState 与 currentState 相同,则直接退出,不用发起调度更新。
- 已经挂载到 queue.pending 上的 update 会在下一次 render 时再次合并。
function dispatchAction<S, A>(fiber: Fiber, queue: UpdateQueue<S, A>, action: A) { // 省略其他部分代码,只保留性能优化部分代码
if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) { const lastRenderedReducer = queue.lastRenderedReducer; if (lastRenderedReducer !== null) { let prevDispatcher; const currentState: S = (queue.lastRenderedState: any); const eagerState = lastRenderedReducer(currentState, action); update.eagerReducer = lastRenderedReducer; update.eagerState = eagerState; if (is(eagerState, currentState)) { return; } } }
scheduleUpdateOnFiber(fiber, lane, eventTime);}