# useState的使用
Hook写法:
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
const [name, setName] = useState('orange');
const [price, setPrice] = useState(200);
return (
// ...
);
}
2
3
4
5
6
7
8
9
10
11
class的写法:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
name: 'orange',
price: 200,
};
}
render() {
return (
// ...
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
疑问:
- React怎么知道哪个state对应哪个
useState?Function Component不像Class Component那样可以将私有状态挂载到类实例中并通过对应的key来指向对应的状态,而且每次的页面的刷新或者说组件的重新渲染都会使得Function重新执行一遍。所以React中必定有一种机制来区分这些Hooks。 - React是如何在每次重新渲染之后都能返回最新的状态?Class Component因为自身的特点可以将私有状态持久化的挂载到类实例上,每时每刻保存的都是最新的值。而Function Component由于本质就是一个函数,并且每次渲染都会重新执行。所以React必定拥有某种机制去记住每一次的更新操作,并最终得出最新的值返回。当然我们还会有其他的一些问题,比如这些状态究竟存放在哪?为什么只能在函数顶层使用Hooks而不能在条件语句等里面使用Hooks?
DETAILS
函数组件通过renderWithHooks函数可以确定当前的workInProgress节点,通过是否存在current节点来判断当前是在Mount阶段还是Update阶段,并获取相应阶段的ReactCurrentDispatcher,执行函数组件自己来获取自身的children。
在执行过程中,会执行对应阶段的hook函数,函数组件的hooks是单向链表结构,存储在Fiber节点的memoizedState属性上,通过next指针(hook.next)依序获取下一个hook对象。在hook对象的queue属性上,存储着hook的更新队列,它是环形单向链表,queue.pending指向最新的update,queue.pending.next则指向的是第一个update。通过执行mountWorkInProgressHook来创建一个新的hook对象,然后返回初始state和触发action的dispatch,通过执行updateReducer来处理queue中的update以获取最新的state。调用dispatch触发action,发起更新任务调度,同时在dispatchAction里计算最新的state,并更新queue环形链表,然后执行scheduleUpdateOnFiber,进入调度,再次进入到renderWithHooks,执行updateReducer,得到新的state值返回,并重新计算渲染。
Function Component Fiber节点:
- type:指向Component,可能是Function Component,也可能是Class Component等其他组件。对Function Component来说,就是一个具体的render函数,比如上面的
Example函数。 - memoizedState:指向自身状态,在Class Fiber下是构造函数声明的state,在Function Fiber下则是一个Hook池。Hook池中维护着组件调用
useXXX产生的所有Hook,Hook中又分别记着各自的状态。

# 源码分析
在项目中使用Hook,必须先引入
import React, { useState } from 'react';
// react/packages/react/src/ReactHooks.js
import ReactCurrentDispatcher from './ReactCurrentDispatcher';
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return ((dispatcher: any): Dispatcher);
}
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
从源码中可以看到,我们调用的其实是ReactCurrentDispatcher.js中的dispatcher.useState():
// react/packages/react/src/ReactCurrentDispatcher.js
import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
/**
* Keeps track of the current dispatcher.
*/
const ReactCurrentDispatcher = {
current: (null: null | Dispatcher),
};
export default ReactCurrentDispatcher;
2
3
4
5
6
7
8
9
10
11
Dispatcher的声明:
// react/packages/react-reconciler/src/ReactInternalTypes.js
export type Dispatcher = {
use?: <T>(Usable<T>) => T,
readContext<T>(context: ReactContext<T>): T,
useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>],
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: (I) => S,
): [S, Dispatch<A>],
useContext<T>(context: ReactContext<T>): T,
useRef<T>(initialValue: T): {current: T},
// 其余Hook的类型定义
};
2
3
4
5
6
7
8
9
10
11
12
13
14
在“react/packages/react-reconciler/src/ReactFiberHooks.new.js”下的useState的具体实现:
全局变量:
// react/packages/react-reconciler/src/ReactFiberHooks.new.js
// 渲染优先级
let renderLanes: Lanes = NoLanes;
// 当前正在构造的fiber, 等同于 workInProgress, 为了和当前hook区分, 所以将其改名
let currentlyRenderingFiber: Fiber = (null: any);
// Hooks被存储在fiber.memoizedState链表上
// 指向current.memoizedState
let currentHook: Hook | null = null; // currentHook = fiber(current).memoizedState
// 指向workInProgress.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;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
调用Function Component函数的主要函数:renderWithHooks:
function updateFunctionComponent(current, workInProgress, Component, nextProps, renderExpirationTime) {
nextChildren = renderWithHooks(current, workInProgress, Component, nextProps, context, renderExpirationTime);
}
2
3
// react/packages/react-reconciler/src/ReactFiberHooks.new.js
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;
// 切换全局指针到当前的Function Component对应的Fiber,然后再去执行render
currentlyRenderingFiber = workInProgress;
// 清除当前fiber的遗留状态
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
// ----------- 2. 调用function,生成子级ReactElement对象 ----
// 指定dispatcher, 区分Mount和Update
// upadte的时候current是有值的
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;
thenableIndexCounter = 0;
return children;
}
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

// react/packages/react-reconciler/src/ReactFiberHooks.new.js
const HooksDispatcherOnMount: Dispatcher = {
useContext: readContext,
useEffect: mountEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
};
const HooksDispatcherOnUpdate: Dispatcher = {
useContext: readContext,
useEffect: updateEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
从这里可以看出,我们的Hooks在Mount阶段和Update阶段的逻辑是不一样的。在Mount阶段和Update阶段他们是两个不同的定义。我们先来看Mount阶段的逻辑。在看之前我们先思考一些问题:React Hooks需要在Mount阶段做什么呢?就拿我们的useState来说:
- 我们需要初始化状态,并返回修改状态的方法,这是最基本的。
- 我们要区分管理每个Hook。
- 提供一个数据结构去存放更新逻辑,以便后续每次更新可以拿到最新的值。
mountState的实现:
// react/packages/react-reconciler/src/ReactFiberHooks.new.js
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
// 获取当前的Hook节点,同时将当前Hook添加到Hook链表中
const hook = mountWorkInProgressHook();
// 赋值初始 state
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, // 用于得到最新state的reducer
lastRenderedState: (initialState: any), // 最后一次得到的state
};
hook.queue = queue;
// 创建dispatch 返回一个dispatch方法用来修改状态,并将此次更新添加update链表中
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue, // 通过函数柯里化,预置这个hook的更新队列
): any));
// 返回当前状态和修改状态的方法
return [hook.memoizedState, dispatch];
}
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

# 如何区分管理Hooks
初始化状态并返回状态和更新状态的方法,这个没有问题,源码也很清晰利用initialState来初始化状态,并且返回了状态和对应更新方法return [hook.memoizedState, dispatch]。那么我们来看看React是如何区分不同的Hooks的,这里我们可以从mountState里的mountWorkInProgressHook方法和Hook的类型定义中找到答案。
export type Hook = {
memoizedState: any, // useState中保存state信息
// useEffect中保存着effect对象;useMemo中保存的是缓存的值和deps;useRef中保存的是ref对象。
baseState: any, // 一次更新中,产生的最新state值
baseQueue: Update<any, any> | null, // 更新队列
queue: any, // 待更新队列
next: Hook | null, // 下一个hook
};
2
3
4
5
6
7
8
这里除了memoizedState字段外,其他字段与之前介绍的UpdateQueue的数据结构字段意义类似。
首先从Hook的类型定义中就可以看到,React对Hooks的定义是链表。也就是说我们组件里使用到的Hooks是通过链表来联系的,上一个Hook的next指向下一个Hook。这些Hooks节点是怎么利用链表数据结构串联在一起的呢?相关逻辑就在每个具体Mount阶段Hooks函数调用的mountWorkInProgressHook方法里:
// react/packages/react-reconciler/src/ReactFiberHooks.new.js
function mountWorkInProgressHook(): Hook {
// 新建链表结点
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
// workInProgressHook可以理解为链表的指针
if (workInProgressHook === null) {
// 特殊情况:指针指的是null,说明链表未建立,链表的头结点。
// 让指针指向hook
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// 一般情况:指针指的不是链表的开头
// 把新的hook赋值给当前workInProgressHook的next并让当前的指针指向下一个钩子
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

上面创建的Hook结构中,通过next指针,实现了一个无环的单向链表。产生的Hook对象依次排列,形成链表存储到函数组件fiber.memoizedState上。
在产生Hook对象过程中,有一个十分重要的指针:workInProgressHook,它通过记录当前生成(更新)的Hook对象,来指向在组件中当前调用到哪个Hook函数了。每调用一次Hook函数,就将这个指针的指向移到该Hook函数产生的Hook对象上。
在Mount阶段,每当我们调用Hooks方法,比如useState,mountState就会调用mountWorkInProgressHook来创建一个Hook节点,并把它添加到Hooks链表上。
# 如何返回最新的值
useState是使用了一个queue链表来存放每一次的更新。以便后面的Update阶段可以返回最新的状态。每次我们调用dispatch的时候,就会形成一个新的updata对象,添加到queue链表上,而且这个是一个循环链表。整个过程可以概括为:
- 创建
update。 - 将
update加入hook.queue中。 - 开启调度。
TIPS
使用环形链表的意义:
整个环形链表变量我们叫它queue,使得queue.pending = update,那么此时queue.pending的最近一次更新,就是update,最早的一次更新是update.next,这样就快速定位到最早的一次更新了。如果是单链表,想找到最早的一次更新,需要一层一层往下找。
queue.pending指向最近一次更新。queue.pending.next指向第一次更新。
dispatchSetState方法的实现:
// react/packages/react-reconciler/src/ReactFiberHooks.new.js
function dispatchSetState<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
const lane = requestUpdateLane(fiber);
// 创建更新的对象
const update: Update<S, A> = {
lane,
action, // 我们需要更新的值
hasEagerState: false, // 是否有提前计算的状态
eagerState: null, //
next: (null: any),
};
if (isRenderPhaseUpdate(fiber)) {
// render 阶段触发的更新 会去创建和更新循环链表
enqueueRenderPhaseUpdate(queue, update);
} else {
// 备用的 fiber
const alternate = fiber.alternate;
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// update队列不存在,意味可以在进入渲染阶段之前就可以计算下一个状态。
// 计算进入渲染阶段之前的下一个阶段的 state,如果新 state 与 当前状态相同,则退出渲染
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
try {
const currentState: S = (queue.lastRenderedState: any);
const eagerState = lastRenderedReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
// 在更新对象上存储下一个状态,以及用于计算的reducer。
// 如果reducer在进入渲染阶段没有被修改,那么就可以使用eager状态了无需再次调用reducer。
enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
return;
}
} catch (error) {
// Suppress the error. It will throw again in the render phase.
} finally {}
}
}
// enqueueConcurrentHookUpdate -> 调用getRootForUpdatedFiber找到当前节点的root节点。
// 获取当前 fiber,根据优先级区分同步任务和异步任务,同步任务应立即同步执行,最先渲染出来,异步任务走scheduler
// scheduleUpdateOnFiber 去调度执行更新任务
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
entangleTransitionUpdate(root, queue, lane);
}
}
markUpdateInDevTools(fiber, lane, action);
}
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
// react/packages/react-reconciler/src/ReactFiberHooks.new.js
function enqueueRenderPhaseUpdate<S, A>(
queue: UpdateQueue<S, A>,
update: Update<S, A>,
) {
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
const pending = queue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
调用多次setCount后count Hook节点的状态:
setCount(1);
setCount(2);
setCount(3);
2
3

TIPS
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// ...
}
2
3
4
5
6
fiber.lanes === NoLanes意味着fiber上不存在update;我们已经知道,通过update计算state发生在声明阶段,这是因为该Hook上可能存在多个不同优先级的update,最终state的值由多个update共同决定。但是当fiber上不存在update,则调用阶段创建的update为该Hook上第一个update,在声明阶段计算state时也只依赖于该update,完全不需要进入声明阶段再计算state。
这样做的好处是:如果计算出的state与该Hook之前保存的state一致,那么完全不需要开启一次调度。即使计算出的state与该Hook之前保存的state不一致,在声明阶段也可以直接使用调用阶段已经计算出的state。
在Hooks节点上面,会通过链表来存放所有的历史更新操作。以便在Update阶段可以通过这些更新获取到最新的值返回给我们。这就是在第一次调用useState之后,每次更新都能返回最新值的原因。然后我们来看看Update阶段,也就是看一下我们的useState是如何利用现有的信息,去给我们返回最新的最正确的值的。updateState实现:
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
2
3
4
5
可以看到,updateState底层调用的其实就是updateReducer,因为我们调用useState的时候,并不会传入reducer,所以这里会默认传递一个basicStateReducer进去。我们先看看这个basicStateReducer:
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
2
3
在使用useState(action)的时候,action通常会是一个值,而不是一个方法。所以basicStateReducer要做的其实就是将这个action返回。
对于更新阶段,说明上一次workInProgress树已经赋值给了current树。存放hooks信息的memoizedState,此时已经存在current树上。对于一次函数组件更新,当再次执行hooks函数的时候,比如useState(0),首先要从current的hooks中找到与当前workInProgressHook,对应的currentHooks,然后复制一份currentHooks给workInProgressHook,接下来hooks函数执行的时候,把最新的状态更新到workInProgressHook,保证hooks状态不丢失。
updateReducer的实现:
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
// 获取当前正在工作中的 Hook,即 workInProgressHook
const hook = updateWorkInProgressHook();
const queue = hook.queue;
if (queue === null) {
throw new Error(
'Should have a queue. This is likely a bug in React. Please file an issue.',
);
}
queue.lastRenderedReducer = reducer;
// currentHook 全局变量,当前 fiber 上的 hook 列表
const current: Hook = (currentHook: any);
let baseQueue = current.baseQueue; // 基础更新队列(初始为null)
// 尚未处理的最后一个待处理更新(初始值为null)
const pendingQueue = queue.pending;
// 如果有待处理的更新,但此时还没有更新的队列在处理,将待处理的更新添加到已有的基础队列的后面
if (pendingQueue !== null) {
if (baseQueue !== null) {
// 将待处理的更新添加基础队列的后面,维护环形链表
const baseFirst = baseQueue.next;
const pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
// pendingQueue赋值给baseQueue
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
// 在处理「更新」的过程中没有产生新的 update,根据优先级来处理 更新队列(baseQueue)上的update
if (baseQueue !== null) {
// queue.pending为最新的update,next则为第一个update
const first = baseQueue.next;
let newState = current.baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let update = first;
// 根据优先级处理环形链表 baseQueue 上的 update
do {
/**
* 对低优先级的 update 进行处理:
* 1、当前的 update 将会被跳过
* 2、将当前的 update 拷贝一份,添加到更新队列的尾部
*/
const updateLane = removeLanes(update.lane, OffscreenLane);
const isHiddenUpdate = updateLane !== update.lane;
// 具体如何判断是否需要跳过这里不讨论
const shouldSkipUpdate = isHiddenUpdate
? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
: !isSubsetOfLanes(renderLanes, updateLane);
if (shouldSkipUpdate) {
// 优先级不足。跳过此更新。
const clone: Update<S, A> = {
lane: updateLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: (null: any),
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
// 如果这是第一个跳过更新,之前的“状态” 就是新的基础“状态”,因为被跳过的更新不会去执行reducer。
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// 更新队列中剩余的优先级。
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane,
);
markSkippedUpdateLanes(updateLane);
} else {
/**
* 对高优先级的 update 进行处理:
* 1、赋值一份当前的 update 进行备份
* 2、调用 reducer 计算出新的 state
*/
if (newBaseQueueLast !== null) {
const clone: Update<S, A> = {
// 这个更新将被提交之后我们永远不想取消提交它。使用NoLane,
// 因为0是所有位掩码的子集,所以这永远不会被上面的检查跳过。
lane: NoLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: (null: any),
};
newBaseQueueLast = newBaseQueueLast.next = clone;
}
const action = update.action; // 我们传入的值,如:setCount(2)
if (update.hasEagerState) {
// 如果这个reducer已经计算就直接用
newState = ((update.eagerState: any): S);
} else {
// 使用 reducer 计算新的 state
newState = reducer(newState, action);
}
}
// 获取链表上的下一个 update
update = update.next;
} while (update !== null && update !== first);
// 通过是否遍历到头结点作为结束条件
// 继续维持循环链表状态
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
// 链表成环
newBaseQueueLast.next = (newBaseQueueFirst: any);
}
// 前后的 state 不同时标识变化
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
// 更新队列上的update 处理完后,在 hook 对象行挂载新的 state 和 update queue
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
if (baseQueue === null) {
// 没有更新队列,优先级置为NoLanes
queue.lanes = NoLanes;
}
const dispatch: Dispatch<A> = (queue.dispatch: any);
// 返回新的 state 和 dispatch 函数
return [hook.memoizedState, dispatch];
}
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147

问题一:这里不是执行最后一个
action不就可以了嘛?(用优先队列保存所有更新)DETAILS
原因很简单,
useState逻辑和useReducer差不多。如果第一个参数是一个函数,会引用上一次update产生的state,所以需要循环调用,每一个update的reducer,如果setCount(1)是这种情况,那么只用更新值,如果是setCount(count => count + 1),那么传入上一次的state得到最新state。问题二:什么情况下会有优先级不足的情况?
DETAILS
这种情况,一般会发生在,当我们调用
setCount时候,调用scheduleUpdateOnFiber渲染当前组件时,又产生了一次新的更新,所以把最终执行reducer更新state任务交给下一次更新。
对更新队列上的update进行处理时,会根据优先级的高低分别做不同的处理:
对于低优先级的
update:- 低优先级的
update拷贝一份,将其添加到新的更新队列尾部。 - 不会计算新的state。
- 低优先级的
对于高优先级的
update- 拷贝当前的
update,将其添加到新的更新队列的尾部。 - 调用
reducer计算新的state。
- 拷贝当前的
updateWorkInProgressHook的实现:
function updateWorkInProgressHook(): Hook {
// 这里在workInProgressHook之外,引入了一个新的概念:currentHook,指的是当前 fiber 上的 hook 列表
let nextCurrentHook: null | Hook;
if (currentHook === null) { // 如果 currentHook = null 证明它是第一个hooks
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else { // 不是第一个hooks,那么指向下一个 hooks
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook: null | Hook;
// 特殊情况:链表开头。如果当前的指针指向为空,说明为开头。注意这里判断的是workInProgressHook
if (workInProgressHook === null) { // 第一次执行hooks
// 这里应该注意一下,当函数组件更新也是调用 renderWithHooks , memoizedState属性是置空的
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
// 如果不是链表开头,那么会让nextWorkInProgressHook等于当前链表指针的next
nextWorkInProgressHook = workInProgressHook.next;
}
// 这个模块是为了让workInProgressHook正常地指向下一个链表节点(因为workInProgressHook不是环形链表)
// 正常情况:初始化nextWorkInProgressHook后,nextWorkInProgressHook不为空
if (nextWorkInProgressHook !== null) {
// 这个情况说明 renderWithHooks 执行 过程发生多次函数组件的执行,我们暂时先不考虑
// 这部分逻辑就是正常将workInProgressHook指针指向下一个元素
workInProgressHook = nextWorkInProgressHook;
// nextWorkInProgressHook在这个方法里不再用到,但是一个全局变量,其他地方可能用到。
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
// 特殊情况:初始化nextWorkInProgressHook后,nextWorkInProgressHook仍然为空
// 可能有两种情况:
// 1.workInProgressHook为空(代表链表为空),currentlyRenderingFiber.memoizedState为空
// 2.workInProgressHook不为空,当前指针指向了链表的尾部,所以workInProgressHook.next为空,
// 导致nextWorkInProgressHook为空。
// 不管链表为空,还是指向了结尾,抽象意义上都是指向了链表结尾。
// 这两种情况,最后都执行了workInProgressHook = newHook;
// Clone from the current hook.
if (nextCurrentHook === null) {
throw new Error('Rendered more hooks than during the previous render.');
}
currentHook = nextCurrentHook;
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
// 处理链表为空的情况
// 这里要注意,如果nextWorkInProgressHook不为空,workInProgressHook也一定不为空。
// 所以这里隐含了一个条件:workInProgressHook和nextWorkInProgressHook都为空。
if (workInProgressHook === null) {
// 链表为空,就需要把第一个元素赋值给指针,让链表指针正常
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
// 当前指针指向了链表的尾部的情况
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
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
68
69
70
71
72
73

- 首先如果是第一次执行hooks函数,那么从
current树上取出memoizedState,也就是旧的hooks。 - 然后声明变量
nextWorkInProgressHook,这里应该值得注意,正常情况下,一次renderWithHooks执行,workInProgress上的memoizedState会被置空,hooks函数顺序执行,nextWorkInProgressHook应该一直为null,那么什么情况下nextWorkInProgressHook不为null,也就是当一次renderWithHooks执行过程中,执行了多次函数组件。这里面的逻辑,实际就是判定,如果当前函数组件执行后,当前函数组件的还是处于渲染优先级,说明函数组件又有了新的更新任务,那么循坏执行函数组件。这就造成了上述的,nextWorkInProgressHook不为null的情况。 - 最后复制
current的hooks,把它赋值给workInProgressHook,用于更新新的一轮hooks状态。
问:为什么要克隆当前Hook,为什么不直接复用?
DETAILS
这样可以保证Fiber上的Hook“原件”完整,某些情况下(比如useEffect要对比新旧Hook的依赖),在构建当前Hook的同时,仍需要上一次Hook的信息。
# 总结
React如何管理区分Hooks?
React通过单链表来管理Hooks,按Hooks的执行顺序依次将Hook节点添加到链表中。
那么 React 怎么知道哪个 state 对应哪个 useState?答案是 React 靠的是 Hook 调用的顺序。
useState如何在每次渲染时,返回最新的值?每个Hook节点通过循环链表记住所有的更新操作,在Update阶段会依次执行
update循环链表中的所有更新操作,最终拿到最新的state返回。为什么不能在条件语句等中使用Hooks?
因为在下一次函数组件更新,Hooks链表结构会被破坏,
current树的memoizedState缓存Hooks信息,和当前workInProgress不一致,如果涉及到读取state等操作,就会发生异常。(如果能保证条件语句每次执行得到的结果都是一样,例如if (true) {...},其实是不会影响链表结构的)Hooks通过什么来证明唯一性?
通过Hooks链表顺序。
Hooks挂载数据的数据结构叫做Fiber,在16版本前,是直接遍历vdom,通过dom api增删改dom的方式来渲染的。引入了fiber架构后,把vdom树转换成fiber链表,然后再渲染fiber。那么Hooks挂载在哪个fiber节点呢?Hooks挂载在renderwithHooks函数中的workInProgress对象上,保存在workInProgress的memorizedState上,它是一个通过next串联的链表。useEffect、useState的实现相对要复杂一点,因为涉及到调度,Hooks会把这些effect串联成一个updateQueue的链表。
参考链接