useState 状态存储
- 初次构造时,会构造一个单链表结构, 挂载到 Fiber.memoizedState 之上
- 当后续更新时,workInProgress 节点就会拷贝之前存储的单链表对象,拿到存储的引用值和对应的更新函数
- React 会从链表头开始,依次调用每个 useState,并从链表中取出相应的状态单元,返回状态值和更新函数
useState 为什么不能写在类似 if 语句中
- React 会按照顺序遍历这个链表,并依次调用 Hook,如果组件执行执行顺序和存储的不一致,会造成状态污染
function MyComponent(props) {
const [state1, setState1] = useState(0);
if (props.condition) {
const [state2, setState2] = useState(0); // 在条件语句中调用 useState
}
const [state3, setState3] = useState(0);
// 组件逻辑
}
- 在第一次渲染时,假设 props.condition 为 true,React 会按顺序调用以下 Hook:
useState(0) -> state1
useState(0) -> state2(在条件语句中)
useState(0) -> state3
- 在下一次渲染时,假设 props.condition 为 false,Hook 的调用顺序变为:
useState(0) -> state1
(跳过条件语句中的 useState)
useState(0) -> state2(原本是 拿 state3 的引用值,实际拿到了 state2)
源码解析
- 初次构造时,遇到use开头的Hook,就会往RootFiber上,挂载一个Hook对象并按照出现顺序构成一个链表结构, 挂载到RootFiber.memoizedState之上.
- 在调用useState的dispatch这样的更新操作时,会往对应的hook对象上的queue上挂载update对象,并注册更新任务回调
- 在执行更新任务时,即updateReducer,每遇到一个use开头的hook,就会执行updateReducer,调用updateWorkInProgressHook(),进行workInProgress、current移动hook链表指针,即对应的workInProgress节点就会拷贝之前的挂载在current树上的hook对象,实现状态的持久化,然后再遍历hook对象上的queue上的所有的update,进行数据更新,再此遇到use开头的hook时,再触发updateReducer,进行上述操作
- 为什么hook不能写在if中,就是因为更新的时候不能调用updateReducer,触发对应挂载在hook上的update进行状态更新
- 并且因为不能触发updateReducer,导致updateWorkInProgressHook进行hook链表指针移动时,如果上一个hook被if隐藏掉了,导致下一个hook实际触发的是上一个hook的updateReducer
- 状态hook,实际调用dispatch进行更新时,是往对应的hook对象的queue上挂载对应的update,并通过scheduler注册一个任务更新回调,真正的状态更新,是在重新执行函数组件时,每遇到一个use开头的hook就会移动hook链表指针,并遍历调用挂载的update对象进行更新数据
fiber与hook相关的属性:
export type Fiber = {|
// 1. fiber节点自身状态相关
pendingProps: any,
memoizedProps: any,
updateQueue: mixed,
memoizedState: any,
// 2. fiber节点副作用(Effect)相关
flags: Flags,
nextEffect: Fiber | null,
firstEffect: Fiber | null,
lastEffect: Fiber | null,
|};
hook相关结构:
type Update<S, A> = {|
lane: Lane,
action: A,
eagerReducer: ((S, A) => S) | null,
eagerState: S | null,
next: Update<S, A>,
priority?: ReactPriorityLevel,
|};
type UpdateQueue<S, A> = {|
pending: Update<S, A> | null,
dispatch: (A => mixed) | null,
lastRenderedReducer: ((S, A) => S) | null,
lastRenderedState: S | null,
|};
export type Hook = {|
memoizedState: any, // 保持在内存中的状态.
baseState: any, // hook.baseQueue中所有update对象合并之后的状态.
baseQueue: Update<any, any> | null, // 存储update对象的环形链表, 只包括高于本次渲染优先级的update对象.
queue: UpdateQueue<any, any> | null, // 存储update对象的环形链表, 包括所有优先级的update对象.
next: Hook | null, // 指向链表中的下一个hook
|};
- 单个Hook拥有自己的状态hook.memoizedState和自己的更新队列hook.queue
hook相关变量
// 渲染优先级
let renderLanes: Lanes = NoLanes;
// 当前正在构造的fiber, 等同于 workInProgress, 为了和当前workInProgressHook区分, 所以将其改名
let currentlyRenderingFiber: Fiber = (null: any);
// Hooks被存储在fiber.memoizedState 链表上
let currentHook: Hook | null = null; // currentHook = fiber(current).memoizedState
let workInProgressHook: Hook | null = null; // workInProgressHook = fiber(workInProgress).memoizedState
// 在function的执行过程中, 是否再次发起了更新. 只有function被完全执行之后才会重置.
// 当render异常时, 通过该变量可以决定是否清除render过程中的更新.
let didScheduleRenderPhaseUpdate: boolean = false;
// 在本次function的执行过程中, 是否再次发起了更新. 每一次调用function都会被重置
let didScheduleRenderPhaseUpdateDuringThisPass: boolean = false;
// 在本次function的执行过程中, 重新发起更新的最大次数
const RE_RENDER_LIMIT = 25;
hook处理
- updateFunctionComponent内部使用了Hook对象, updateClassComponent内部使用了class实例
// 只保留FunctionComponent相关:
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const updateLanes = workInProgress.lanes;
switch (workInProgress.tag) {
//函数组件
case FunctionComponent: {
const Component = workInProgress.type;
//获取新props
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
}
}
function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderLanes,
) {
// ...省略无关代码
let context;
let nextChildren;
prepareToReadContext(workInProgress, renderLanes);
// 进入Hooks相关逻辑, 最后返回下级ReactElement对象
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
// 进入reconcile函数, 生成下级fiber节点
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
// 返回下级fiber节点
return workInProgress.child;
}
renderWithHooks
- 通过这个函数,fiber和hook产生了关联
// ...省略无关代码
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
// --------------- 1. 设置全局变量 -------------------
renderLanes = nextRenderLanes; // 当前渲染优先级
currentlyRenderingFiber = workInProgress; // 当前fiber节点, 也就是function组件对应的fiber节点
// 清除当前fiber的遗留状态
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
// --------------- 2. 调用function,生成子级ReactElement对象 -------------------
// 指定dispatcher, 区分mount和update
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
// 执行function函数, 其中进行分析Hooks的使用
let children = Component(props, secondArg);
// --------------- 3. 重置全局变量,并返回 -------------------
// 执行function之后, 还原被修改的全局变量, 不影响下一次调用
renderLanes = NoLanes;
currentlyRenderingFiber = (null: any);
currentHook = null;
workInProgressHook = null;
didScheduleRenderPhaseUpdate = false;
return children;
}
基本demo
import { useState, useEffect } from 'react';
export default function App() {
// 1. useState
const [a, setA] = useState(1);
// 2. useEffect
useEffect(() => {
console.log(`effect 1 created`);
});
// 3. useState
const [b] = useState(2);
// 4. useEffect
useEffect(() => {
console.log(`effect 2 created`);
});
return (
<>
<button onClick={() => setA(a + 1)}>{a}</button>
<button>{b}</button>
</>
);
}
- 共使用了 4 次Hook api, 依次调用useState, useEffect, useState, useEffect.
初次构造
- useState在fiber初次构造时对应mountState
- useReducer在fiber初次构造时对应mountReducer
- useEffect在fiber初次构造时分别对应mountEffect->mountEffectImpl
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
//新建hook对象并移动workInProgressHook指针
const hook = mountWorkInProgressHook();
//拿到初始化hook状态
hook.memoizedState = hook.baseState = initialState;
//创建hook.queue,basicStateReducer为useState内置的reducer
const queue = (hook.queue = {
pending: null,
dispatch: null,
// queue.lastRenderedReducer是内置函数
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
});
//创建dispatch
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
//返回[当前状态, dispatch函数]
return [hook.memoizedState, dispatch];
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
const hook = mountWorkInProgressHook();
// ...
}
mountWorkInProgressHook
function mountWorkInProgressHook(): Hook {
//新建一个hook对象
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
//workInProgressHook即fiber(workInProgress).memoizedState
if (workInProgressHook === null) {
// 链表中首个hook,currentlyRenderingFiber即workInProgress
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// 将hook添加到链表末尾
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
basicStateReducer
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
//对应更新state时的2种方式
dispatch({ count: 1 }); // 1.直接设置
dispatch(state => ({ count: state.count + 1 })); // 2.通过回调函数设置
- 可见, useState就是对useReducer的基本封装, 内置了一个特殊的reducer
- 创建hook之后返回值[hook.memoizedState, dispatch]中的dispath实际上会调用reducer函数.
mountReducer
function mountReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
// 1. 创建hook并移动指针
const hook = mountWorkInProgressHook();
//拿到初始状态
let initialState;
if (init !== undefined) {
initialState = init(initialArg);
} else {
initialState = ((initialArg: any): S);
}
// 2. 初始化hook的属性
// 保存状态
hook.memoizedState = hook.baseState = initialState;
// 2.2 设置 hook.queue
const queue = (hook.queue = {
pending: null,
dispatch: null,
// queue.lastRenderedReducer是由外部传入
lastRenderedReducer: reducer,
lastRenderedState: (initialState: any),
});
// 2.3 设置 hook.dispatch
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
// 3. 返回[当前状态, dispatch函数]
return [hook.memoizedState, dispatch];
}
- 初次构造时,通过调用mountWorkInProgressHook,通过链表的方式挂载每一个hook到currentlyRenderingFiber即workInProgress的memoizedState属性上
fiber树构造(对比更新)阶段
基本demo
import { useState } from 'react';
export default function App() {
const [count, dispatch] = useState(0);
return (
<button
onClick={() => {
dispatch(1);
dispatch(3);
dispatch(2);
}}
>
{count}
</button>
);
}
初始化时内部状态
点击button, 通过dispatch函数进行更新
- 将update对象添加到hook.queue.pending环形链表
- 环形链表的特征: 为了方便添加新元素和快速拿到队首元素(都是O(1)), 所以pending指针指向了链表中最后一个元素
- 发起调度更新: 调用scheduleUpdateOnFiber, 进入reconciler 运作流程中的输入阶段
- 每一个dispatch对应的update,都会挂载到这个hook.queue上
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
// 1. 创建update对象
const eventTime = requestEventTime();
//获取优先级
const lane = requestUpdateLane(fiber); // Legacy模式返回SyncLane
//创建update对象
const update: Update<S, A> = {
lane,
action,
eagerReducer: null,
eagerState: null,
next: (null: any),
};
// 2. 将update对象添加到hook.queue.pending队列
//是一个环形链表
const pending = queue.pending;
if (pending === null) {
// 首个update, 创建一个环形链表指向首部
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
//queue.pending始终为环形链表最后一个,.next指向首个update
queue.pending = update;
//获取workInProgress
const alternate = fiber.alternate;
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
// 渲染时更新, 做好全局标记
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
} else {
// 性能优化部分
// 下面这个if判断, 能保证当前创建的update, 是`queue.pending`中第一个`update`. 为什么? 发起更新之后fiber.lanes会被改动, 如果`fiber.lanes && alternate.lanes`没有被改动, 自然就是首个update
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
//获取reducer
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
const currentState: S = (queue.lastRenderedState: any);
//获取最新state
const eagerState = lastRenderedReducer(currentState, action);
// 暂存`eagerReducer`和`eagerState`, 如果在render阶段reducer==update.eagerReducer, 则可以直接使用无需再次计算
// 即下面的updateReducer中,会用到eagerReducer来优化是否需要再次执行reducer获取state
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
// 快速通道, eagerState与currentState相同, 无需调度更新
// 注: update已经被添加到了queue.pending, 并没有丢弃. 之后需要更新的时候, 此update还是会起作用
return;
}
}
}
// 3. 发起调度更新, 进入`reconciler 运作流程`中的输入阶段.
scheduleUpdateOnFiber(fiber, lane, eventTime);
}
}
- 即便多个dispatch,会通过任务调度中的类似防抖节流的优化,只注册一次任务
ensureRootIsScheduled
//计算当前fiber的优先级
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
//计算回掉的优先级
const newCallbackPriority = returnNextLanesPriority();
//没有优先级直接返回
if (nextLanes === NoLanes) {
return;
}
//首次执行为null,不会进入
if (existingCallbackNode !== null) {
//existingCallbackPriority为上一次计算的结果
const existingCallbackPriority = root.callbackPriority;
//如果再次调用,同个节点的任务优先级相同,直接返回
if (existingCallbackPriority === newCallbackPriority) {
return;
}
//如果同个节点的任务优先级不同,取消上次的任务
cancelCallback(existingCallbackNode);
}
...
updateFunctionComponent->renderWithHooks
-
当dispatch发起调度进行更新后,会克隆RootFiber上的hook链表,然后遍历每一个hook上的queue上的update对象,挂在的update操作由上面的dispatch函数完成
-
每次遇到useState在fiber对比更新对应调用updateState->updateReducer
- 这也解释了为什么hook不能写在if中,因为如果没有遇到useState,就不会调用此循环操作进行数据更新
-
每次遇到useEffect在fiber对比更新对应调用updateEffect->updateEffectImpl
// ----- 状态Hook --------
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
//从current.memoizedState上克隆hook并移动指针,获取workInProgress
const hook = updateWorkInProgressHook();
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const current: Hook = (currentHook: any);
let baseQueue = current.baseQueue;
// 2. 链表拼接: 将 hook.queue.pending 拼接到 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上
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
// 3. 状态计算
if (baseQueue !== null) {
const first = baseQueue.next;
let newState = current.baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let update = first;
//在更新时,每遇到useState,都会执行此操作循环update对象
//这也解释了为什么hook不能写在if中,因为如果没有遇到useState,就不会调用此循环操作进行数据更新
do {
const updateLane = update.lane;
// 3.1 优先级提取update
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// 优先级不够: 加入到baseQueue中, 等待下一次render
const clone: Update<S, A> = {
lane: updateLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: (null: any),
};
//拼接操作
if (newBaseQueueLast === null) {
//更新newBaseQueueFirst
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
//更新newBaseQueueLast
newBaseQueueLast = newBaseQueueLast.next = clone;
}
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane,
);
markSkippedUpdateLanes(updateLane);
} else {
// 优先级足够: 状态合并
if (newBaseQueueLast !== null) {
// 更新baseQueue
const clone: Update<S, A> = {
lane: NoLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: (null: any),
};
//更新newBaseQueueLast
newBaseQueueLast = newBaseQueueLast.next = clone;
}
if (update.eagerReducer === reducer) {
// 性能优化: 如果存在 update.eagerReducer, 直接使用update.eagerState.避免重复调用reducer
newState = ((update.eagerState: any): S);
} else {
const action = update.action;
// 调用reducer获取最新状态
newState = reducer(newState, action);
}
}
//切换下一个update
update = update.next;
} while (update !== null && update !== first);
// 3.2. 更新属性
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
newBaseQueueLast.next = (newBaseQueueFirst: any);
}
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
// 把计算之后的结果更新到workInProgressHook上
hook.memoizedState = newState;
//当存在低优先级的update时,会保存低优先级update的状态
hook.baseState = newBaseState;
//当有低优先级更新时,用来保存update链表,留到下次更新调用
//也是个环形链表
//当没有低优先级update时,newBaseQueueLast为null
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
// ----- 副作用Hook --------
function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
const hook = updateWorkInProgressHook();
// ...
}
updateWorkInProgressHook
- currentHook和workInProgressHook两个指针同时向后移动进行hook的克隆操作
- 从currentHook克隆而来的newHook.next=null, 进而workInProgressHook链表需要完全重建.
function updateWorkInProgressHook(): Hook {
// 1. 移动currentHook指针,即current.memoizedState上的hook链表
let nextCurrentHook: null | Hook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
//首次克隆
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
//移动指针
nextCurrentHook = currentHook.next;
}
// 2. 移动workInProgressHook指针
let nextWorkInProgressHook: null | Hook;
if (workInProgressHook === null) {
//currentlyRenderingFiber即workInProgress
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
//因克隆hook的next为null,所以nextWorkInProgressHook为null,继续走克隆hook流程
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
// 渲染时更新
} else {
currentHook = nextCurrentHook;
// 3. 克隆currentHook作为新的workInProgressHook.
// 随后逻辑与mountWorkInProgressHook一致
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null, // 注意next指针是null
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
//继续根据current.memoizedState上挂载的hook链表进行克隆并移动workInProgressHook指针
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
- 因为更新会拿到上一次的hook对象,所以状态并不会丢失
- 利用fiber树内部的双缓冲技术, 实现了Hook从current到workInProgress转移, 进而实现了Hook状态的持久化
克隆hook操作
最后调用dispatch进行更新后