function 类型的 fiber 节点, 它的处理函数是 updateFunctionComponent, 其中再通过 renderWithHooks 调用 function.
在 function 中, 通过 Hook Api(如: useState, useEffect)创建 Hook 对象.
状态 Hook 实现了状态持久化(等同于 class 组件维护 fiber.memoizedState).
副作用 Hook 则实现了维护 fiber.flags,并提供副作用回调(类似于 class 组件的生命周期回调)
多个 Hook 对象构成一个链表结构, 并挂载到 fiber.memoizedState 之上.
fiber 树更新阶段, 把 current.memoizedState 链表上的所有 Hook 按照顺序克隆到 workInProgress.memoizedState 上, 实现数据的持久化.
创建 Hook
在 fiber 初次构造阶段, useState 对应源码 mountState, useReducer 对应源码 mountReducer
mountState:
1function mountState<S>(2 initialState: (() => S) | S,3): [S, Dispatch<BasicStateAction<S>>] {4 // 1. 创建hook5 const hook = mountWorkInProgressHook();6 if (typeof initialState === 'function') {7 initialState = initialState();8 }9 // 2. 初始化hook的属性10 // 2.1 设置 hook.memoizedState/hook.baseState11 // 2.2 设置 hook.queue12 hook.memoizedState = hook.baseState = initialState;13 const queue = (hook.queue = {14 pending: null,15 dispatch: null,16 // queue.lastRenderedReducer是内置函数17 lastRenderedReducer: basicStateReducer,18 lastRenderedState: (initialState: any),19 });20 // 2.3 设置 hook.dispatch21 const dispatch: Dispatch<22 BasicStateAction<S>,23 > = (queue.dispatch = (dispatchAction.bind(24 null,25 currentlyRenderingFiber,26 queue,27 ): any));2829 // 3. 返回[当前状态, dispatch函数]30 return [hook.memoizedState, dispatch];31}
mountReducer:
1function mountReducer<S, I, A>(2 reducer: (S, A) => S,3 initialArg: I,4 init?: I => S,5): [S, Dispatch<A>] {6 // 1. 创建hook7 const hook = mountWorkInProgressHook();8 let initialState;9 if (init !== undefined) {10 initialState = init(initialArg);11 } else {12 initialState = ((initialArg: any): S);13 }14 // 2. 初始化hook的属性15 // 2.1 设置 hook.memoizedState/hook.baseState16 hook.memoizedState = hook.baseState = initialState;17 // 2.2 设置 hook.queue18 const queue = (hook.queue = {19 pending: null,20 dispatch: null,21 // queue.lastRenderedReducer是由外传入22 lastRenderedReducer: reducer,23 lastRenderedState: (initialState: any),24 });25 // 2.3 设置 hook.dispatch26 const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(27 null,28 currentlyRenderingFiber,29 queue,30 ): any));3132 // 3. 返回[当前状态, dispatch函数]33 return [hook.memoizedState, dispatch];34}
mountState 和 mountReducer 逻辑简单: 主要负责创建 hook, 初始化 hook 的属性, 最后返回[当前状态, dispatch 函数].
唯一的不同点是 hook.queue.lastRenderedReducer:
- mountState 使用的是内置的 basicStateReducer
1function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {2 return typeof action === "function" ? action(state) : action;3}
- mountReducer 使用的是外部传入自定义 reducer
可见 mountState 是 mountReducer 的一种特殊情况, 即 useState 也是 useReducer 的一种特殊情况, 也是最简单的情况.
useState 可以转换成 useReducer:
1const [state, dispatch] = useState({ count: 0 });23// 等价于4const [state, dispatch] = useReducer(5 function basicStateReducer(state, action) {6 return typeof action === "function" ? action(state) : action;7 },8 { count: 0 }9);1011// 当需要更新state时, 有2种方式12dispatch({ count: 1 }); // 1.直接设置13dispatch((state) => ({ count: state.count + 1 })); // 2.通过回调函数设置
可见, useState 就是对 useReducer 的基本封装, 内置了一个特殊的 reducer(后文不再区分 useState, useReducer, 都以 useState 为例).创建 hook 之后返回值[hook.memoizedState, dispatch]中的 dispath 实际上会调用 reducer 函数.
状态初始化
在useState(initialState)
函数内部, 设置hook.memoizedState = hook.baseState = initialState;
, 初始状态被同时保存到了hook.baseState,hook.memoizedState
中.
- hook.memoizedState: 当前状态
- hook.baseState: 基础状态, 作为合并 hook.baseQueue 的初始值(下文介绍).
最后返回[hook.memoizedState, dispatch], 所以在 function 中使用的是 hook.memoizedState.
状态更新
有如下组件
1import { useState } from "react";2export default function App() {3 const [count, dispatch] = useState(0);4 return (5 <button6 onClick={() => {7 dispatch(1);8 dispatch(3);9 dispatch(2);10 }}11 >12 {count}13 </button>14 );15}
初次渲染时 count = 0, 这时 hook 对象的内存状态如下:
点击 button, 通过 dispatch 函数进行更新, dispatch 实际就是 dispatchAction:
1function dispatchAction<S, A>(2 fiber: Fiber,3 queue: UpdateQueue<S, A>,4 action: A,5) {6 // 1. 创建update对象7 const eventTime = requestEventTime();8 const lane = requestUpdateLane(fiber); // Legacy模式返回SyncLane9 const update: Update<S, A> = {10 lane,11 action,12 eagerReducer: null,13 eagerState: null,14 next: (null: any),15 };1617 // 2. 将update对象添加到hook.queue.pending队列18 const pending = queue.pending;19 if (pending === null) {20 // 首个update, 创建一个环形链表21 update.next = update;22 } else {23 update.next = pending.next;24 pending.next = update;25 }26 queue.pending = update;2728 const alternate = fiber.alternate;29 if (30 fiber === currentlyRenderingFiber ||31 (alternate !== null && alternate === currentlyRenderingFiber)32 ) {33 // 渲染时更新, 做好全局标记34 didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;35 } else {36 // ...省略性能优化部分, 下文介绍3738 // 3. 发起调度更新, 进入`reconciler 运作流程`中的输入阶段.39 scheduleUpdateOnFiber(fiber, lane, eventTime);40 }41}
创建 update 对象, 其中 update.lane 代表优先级(可回顾 fiber 树构造(基础准备)中的 update 优先级).
将 update 对象添加到 hook.queue.pending 环形链表. 环形链表的特征: 为了方便添加新元素和快速拿到队首元素(都是 O(1)), 所以 pending 指针指向了链表中最后一个元素.
发起调度更新: 调用 scheduleUpdateOnFiber, 进入 reconciler 运作流程中的输入阶段.
本示例中虽然同时执行了 3 次 dispatch, 会请求 3 次调度, 由于调度中心的节流优化, 最后只会执行一次渲染
在 fiber 树构造(对比更新)过程中, 再次调用 function, 这时 useState 对应的函数是 updateState
1function updateState<S>(2 initialState: (() => S) | S,3): [S, Dispatch<BasicStateAction<S>>] {4 return updateReducer(basicStateReducer, (initialState: any));5}
实际调用 updateReducer.
在执行 updateReducer 之前, hook 相关的内存结构如下:
执行:
1function updateReducer<S, I, A>(2 reducer: (S, A) => S,3 initialArg: I,4 init?: I => S,5): [S, Dispatch<A>] {6 // 1. 获取workInProgressHook对象7 const hook = updateWorkInProgressHook();8 const queue = hook.queue;9 queue.lastRenderedReducer = reducer;10 const current: Hook = (currentHook: any);11 let baseQueue = current.baseQueue;1213 // 2. 链表拼接: 将 hook.queue.pending 拼接到 current.baseQueue14 const pendingQueue = queue.pending;15 if (pendingQueue !== null) {16 if (baseQueue !== null) {17 const baseFirst = baseQueue.next;18 const pendingFirst = pendingQueue.next;19 baseQueue.next = pendingFirst;20 pendingQueue.next = baseFirst;21 }22 current.baseQueue = baseQueue = pendingQueue;23 queue.pending = null;24 }25 // 3. 状态计算26 if (baseQueue !== null) {27 const first = baseQueue.next;28 let newState = current.baseState;2930 let newBaseState = null;31 let newBaseQueueFirst = null;32 let newBaseQueueLast = null;33 let update = first;3435 do {36 const updateLane = update.lane;37 // 3.1 优先级提取update38 if (!isSubsetOfLanes(renderLanes, updateLane)) {39 // 优先级不够: 加入到baseQueue中, 等待下一次render40 const clone: Update<S, A> = {41 lane: updateLane,42 action: update.action,43 eagerReducer: update.eagerReducer,44 eagerState: update.eagerState,45 next: (null: any),46 };47 if (newBaseQueueLast === null) {48 newBaseQueueFirst = newBaseQueueLast = clone;49 newBaseState = newState;50 } else {51 newBaseQueueLast = newBaseQueueLast.next = clone;52 }53 currentlyRenderingFiber.lanes = mergeLanes(54 currentlyRenderingFiber.lanes,55 updateLane,56 );57 markSkippedUpdateLanes(updateLane);58 } else {59 // 优先级足够: 状态合并60 if (newBaseQueueLast !== null) {61 // 更新baseQueue62 const clone: Update<S, A> = {63 lane: NoLane,64 action: update.action,65 eagerReducer: update.eagerReducer,66 eagerState: update.eagerState,67 next: (null: any),68 };69 newBaseQueueLast = newBaseQueueLast.next = clone;70 }71 if (update.eagerReducer === reducer) {72 // 性能优化: 如果存在 update.eagerReducer, 直接使用update.eagerState.避免重复调用reducer73 newState = ((update.eagerState: any): S);74 } else {75 const action = update.action;76 // 调用reducer获取最新状态77 newState = reducer(newState, action);78 }79 }80 update = update.next;81 } while (update !== null && update !== first);8283 // 3.2. 更新属性84 if (newBaseQueueLast === null) {85 newBaseState = newState;86 } else {87 newBaseQueueLast.next = (newBaseQueueFirst: any);88 }89 if (!is(newState, hook.memoizedState)) {90 markWorkInProgressReceivedUpdate();91 }92 // 把计算之后的结果更新到workInProgressHook上93 hook.memoizedState = newState;94 hook.baseState = newBaseState;95 hook.baseQueue = newBaseQueueLast;96 queue.lastRenderedState = newState;97 }9899 const dispatch: Dispatch<A> = (queue.dispatch: any);100 return [hook.memoizedState, dispatch];101}
调用 updateWorkInProgressHook 获取 workInProgressHook 对象
链表拼接: 将 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 时再次合并.
1function dispatchAction<S, A>(2 fiber: Fiber,3 queue: UpdateQueue<S, A>,4 action: A,5) {6 // ...省略无关代码 ...只保留性能优化部分代码:78 // 下面这个if判断, 能保证当前创建的update, 是`queue.pending`中第一个`update`. 为什么? 发起更新之后fiber.lanes会被改动(可以回顾`fiber 树构造(对比更新)`章节), 如果`fiber.lanes && alternate.lanes`没有被改动, 自然就是首个update9 if (10 fiber.lanes === NoLanes &&11 (alternate === null || alternate.lanes === NoLanes)12 ) {13 const lastRenderedReducer = queue.lastRenderedReducer;14 if (lastRenderedReducer !== null) {15 let prevDispatcher;16 const currentState: S = (queue.lastRenderedState: any);17 const eagerState = lastRenderedReducer(currentState, action);18 // 暂存`eagerReducer`和`eagerState`, 如果在render阶段reducer==update.eagerReducer, 则可以直接使用无需再次计算19 update.eagerReducer = lastRenderedReducer;20 update.eagerState = eagerState;21 if (is(eagerState, currentState)) {22 // 快速通道, eagerState与currentState相同, 无需调度更新23 // 注: update已经被添加到了queue.pending, 并没有丢弃. 之后需要更新的时候, 此update还是会起作用24 return;25 }26 }27 }28 // 发起调度更新, 进入`reconciler 运作流程`中的输入阶段.29 scheduleUpdateOnFiber(fiber, lane, eventTime);30}
验证demo