- 关于React源码的阅读的版本都为18.2.0。 因为这一系列是源码系列, 故源码相关会讲得比较详细。
- 如果对源码不太感冒的话可以直接看第三大标题的图示总结部分, 主流程也都呈现了
一、入口源码解析
为了让干扰最小化, 我们来个最简单的代码
js
function App() {
const [count, setCount] = useState(1);
return (
<div>
<div onClick={() => setCount(count + 1)}>{count}</div>
</div>
);
}
关于整体的render
过程我们之前在文章三 React源码阅读(三)- render已经接触过了, 其中我们在对Fiber
进行构造的时候。 是通过在beginwork
的阶段通过Swtich Case
然后识别Fiber
的tag
再进入不同的流程中。
我们断点直接打到APP
。可以看到在beginwork
的时候判断其tag
为IndeterminateComponent
(尚未确定具体类型的组件)。 然后调用了mountIndeterminateComponent
。 里面又继续调用了renderWithHooks
renderWithHooks
这个命名一看就是我们想要,需要了解的, 那我们直接聚焦到renderWithHooks
renderWithHooks
可以看到首先是对一些状态做了初始化。 然后给ReactCurrentDispatcher.current
进行了赋值。如果是首次渲染的话赋值HooksDispatcherOnMount
, 如果是更新的情况的话赋值HooksDispatcherOnUpdate
。 接着调用了组件函数本身。 走入了我们的组件
js
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null, // 当前页面使用的Fiber结构
workInProgress: Fiber, // 当前正在构造的Fiber结构
Component: (p: Props, arg: SecondArg) => any, // 当前处理的组件
props: Props, // 传递给组件的值
secondArg: SecondArg,
nextRenderLanes: Lanes, // 下一次render的优先级
): any {
renderLanes = nextRenderLanes;
// 设置当前进行render的Fiber节点。 这个在后面会使用
currentlyRenderingFiber = workInProgress;
// 进行初始化
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
// 如果是初次渲染的时候除了hostRootFiber之外其他节点的current都为空
// 故通过current判断是初次渲染还是更新
// 然后挂载不同的Dispatcher
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
// 这里去调用组件--------也就走入了我们的函数本身
let children = Component(props, secondArg);
----------这里先打断, 后面的源码后续再看, 跟着代码逻辑先进入函数本身-------------------
HooksDispatcherOnMount/HooksDispatcherOnUpdate
上面我们也说到了, 这两玩意是赋值给ReactCurrentDispatcher.current
。 两者都是对象。对象中的key
都以hook
的名称进行命名, value
的话对于HooksDispatcherOnMount
而言基本都是mountxxxx
, 以mount
开头。 对于HooksDispatcherOnUpdate
而言就是基本都是updatexxxx
, 以update
开头。 两者分别对应的都是一些函数逻辑。 在后续讲解不同hook
的时候会具体描述
这里如果你处于开发环境的话其实赋值的是
HooksDispatcherOnMountWithHookTypesInDEV
和HooksDispatcherOnMountInDEV
。 目的是在开发的时候React
能够提供更多的警告和提示信息。 这部分我们不需要了解。 核心逻辑直接看线上版本即可
二、 useState源码解析
我们从
useState
的逻辑打开hook的大门。 主打一个举一反三。 故useState
的相关逻辑会讲述的比较清晰。 在重要函数中使用的函数基本都会解析一番。 如果有疑问的直接点击目录进行定位即可
如图所示, 调用APP()
之后进来了useState
,useState
的代码很简单, 就调用了resolveDispatcher()
, 该函数返回了dispatcher
, 然后调用该dispatcher
的useState
方法, 那么我们先看看这个dispatcher
是啥
js
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
resolveDispatcher
进来一看,其实就是赋值了ReactCurrentDispatcher.current
。 诶, 这不就是我们上面讲述的东西吗。 此时为初次渲染。那么我们此时拿到的应该就是HooksDispatcherOnMount
- 继续往下走。 可以看到调用
dispatcher.useState(initialState);
的时候。 那么实际上是调用了mountState
mountState
因为他的返回值就是useState
的返回值。 故我们清楚最后返回的肯定是一个数组。 数组的首项为当前维护的state
的值。 数组的第二项为可以修改state
的函数。
故该函数的工作都是围绕此展开
函数组件不像类组件可以直接生成对应的实例。 故函数维护的状态都是挂载到Fiber
节点上。 该函数的逻辑就是生成当前Fiber
对应的hook
对象, 对其进行各种初始化, 然后再挂载到当前Fiber
的memoizedState
。 最后返回的都是挂载当前hook
上的东西
- 最后返回的
state
:hook.memoizedState
- 最后返回的
Set
函数:hook.queue.dispatch
(dispatchSetState
)
js
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
// 生成当前的hook对象, 并挂上链表
const hook = mountWorkInProgressHook();
// initalState是可以传入一个函数的。此时我们记录的值是该函数的返回值
// 情景: 初始值需要通过复杂的计算获得
if (typeof initialState === 'function') {
initialState = initialState();
}
// 初始化当前hook的memoizedState和baseState, 都赋值为传入的initialState(或者调用得到的)
hook.memoizedState = hook.baseState = initialState;
// 生成一个queue对象
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any), // 这里也对其初始化了
};
// 也挂载到hook对象
hook.queue = queue;
// 这里其实就是将dispatchSetState赋值给了queue.dispatch和dispatch
// 那么如果接下去如果触发state的改变的话就是触发了dispatchSetState
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
mountWorkInProgressHook
这个函数还蛮重要经典的。 主要就是生成hook
对象。 然后挂上当前Fiber
的hook
链表(挂在memoizedState
上)。 用workInProgressHook
作为指针, 始终指向当前链表的最后一个。
js
function mountWorkInProgressHook(): Hook {
// 生成当前的hook对象
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null, // 看到了next就应该知道他是要形成对应的链表
};
// 如果当前Fiber的workInProgressHook还没有初始化的时候对其进行初始化
if (workInProgressHook === null) {
// 前面我们在renderWithHooks的时候就设置了currentlyRenderingFiber为workInprogress, 也就是当前的Fiber
// 故这里是当workInProgressHook指向当前生成的hook对象
// 然后挂到当前Fiber的memoizedState上
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// 如果已经初始化过的话, 那么就直接找到next挂上去。
// 且workInProgressHook指向他的next。
// 让workInProgressHook始终指向链表的最后一个, 从而能够继续挂上其他hook对象
workInProgressHook = workInProgressHook.next = hook;
}
// 返回当前指针
return workInProgressHook;
}
既然我们提到了链表的思想。 那么我们不妨改一下代码。 我让代码中存在多个state, 然后我们再观察一下我们的链表。 看看是否符合预期
js
const [count, setCount] = useState(1);
const [count1, setCount1] = useState(2);
const [count2, setCount2] = useState(3);
const [count3, setCount3] = useState(() => 4);
左边是我画的图, 右边是直接断点的截图。 爱看哪个看哪个hhh
dispatchSetState
继续看dispatchSetState
。我们在前面的代码中使用click
去调用setCount
触发count
的变化。 那么就自然会走到dispatchSetState
函数中
js
function dispatchSetState<S, A>(
fiber: Fiber, // 这个之前挂载的时候就传入了, 为当前节点的Fiber
queue: UpdateQueue<S, A>, // 当前节点的hook.queue
action: A, // 这里的action就是我们传入的东西。 可以为值也可以是函数
) {
// 获取优先级
const lane = requestUpdateLane(fiber);
// 生成了一个update对象
const update: Update<S, A> = {
lane,
action,
hasEagerState: false,
eagerState: null,
next: (null: any),
};
// 判断是否处于render更新阶段
if (isRenderPhaseUpdate(fiber)) {
// 是的话将update对象加入queue的pending链表上
enqueueRenderPhaseUpdate(queue, update);
} else {
const alternate = fiber.alternate;
// 判断是否已有更新
// 这里可以定位到enqueueConcurrentHookUpdate看
// 其中有对Fiber.lanes和alternate.lanes进行合并处理
// 那么当我们再走进这个逻辑的时候,如果已经有过更新,此处就不会为NoLanes
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// 在初始化queue对象的时候, 我们给queue.lastRenderedReducer就赋值了basicStateReducer
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
// 这里初始化的时候也是赋值了initialState
const currentState: S = (queue.lastRenderedState: any);
// 传入当前的state和传入的action, 拿到期望的更新之后的state
const eagerState = lastRenderedReducer(currentState, action);
// 表示该update对象的期望state已经计算出来了并赋值
update.hasEagerState = true;
update.eagerState = eagerState;
// 进行浅比较, 如果一致的话将update推入栈之后直接return。
// 如果没有其他更新的话则不会进入调度过程了
if (is(eagerState, currentState)) {
// 推入栈
enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
return;
}
}
}
// 入栈入栈
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
const eventTime = requestEventTime();
// 开启调度
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
entangleTransitionUpdate(root, queue, lane);
}
}
}
isRenderPhaseUpdate
这里是用来判断是否处于render
阶段的。我们前面有涉及到currentlyRenderingFiber
是在renderWithHooks
的时候进行赋值的。 那renderWithHooks
其实就是处于render
阶段的。 在点击触发state
的修改过程中。 我们触发onClick
之后就直接走到了dispatchSetState
,没有经过renderWithHooks
, 故此时拿到的currentlyRenderingFiber
就为空。 这里是判断了当前Fiber
以及他的alternate Fiber
。只要有一个处于render
阶段就返回true
。 否则返回false
js
function isRenderPhaseUpdate(fiber: Fiber) {
const alternate = fiber.alternate;
return (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
);
}
enqueueRenderPhaseUpdate
这里用于处理state
触发变更时刚好处于render
阶段的情况。 此时会将update
对象存放到queue
的pending
链上,也是以链表的方法存储。 在这个render
过程之后, 我们将会重新启动更新
js
**function enqueueRenderPhaseUpdate<S, A>(
queue: UpdateQueue<S, A>,
update: Update<S, A>,
) {
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
const pending = queue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
最终形成的链表如图所示。 第一个update
对象为queue.pending.next
basicStateReducer
对于action
而言, 可以传入的参数有两种。 一种是直接传值。 一种是传入函数。 如果是函数的话函数的参数为当前的state
。 故该函数就是处理了这两种情况。 这里的state
就是目前最新的state
, 我们常用函数的方式处理闭包可能带来的问题。
js
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
enqueueConcurrentHookUpdate
这里就是推入栈中保存。这里调用的是enququeUpdate
。 我们之前在 React源码入门篇(一)--- enqueueUpdate 就已经讲过该逻辑的。 主要就是将queue
和update
推入栈。 然后更新了Fiber
的lanes
js
export function enqueueConcurrentHookUpdate<S, A>(
fiber: Fiber,
queue: HookQueue<S, A>,
update: HookUpdate<S, A>,
lane: Lane,
): FiberRoot | null {
const concurrentQueue: ConcurrentQueue = (queue: any);
const concurrentUpdate: ConcurrentUpdate = (update: any);
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
return getRootForUpdatedFiber(fiber);
}
enqueueConcurrentHookUpdateAndEagerlyBailout
这里和enqueueConcurrentHookUpdate
其实内容上基本一致。 都会把update
推入栈中保存。 但是不一样的是, 这里不需要return
东西出去了。 以及他的上下文中。 执行完这个函数之后直接return
了。
既然return
了如果没有其他State
变更的话就不会走到下面的scheduleUpdateOnFiber
, 不会走到render
阶段, 那么这个函数自然也就不会再次被调用
我们可以试一下
小总结
先总结一下dispatchSetState
这个函数做了什么, 我们传入Set
函数的值,其实就是作为action
参数传入了dispatchSetState
。 然后我们生成了对应的update
对象,其中就包含action
。 然后将update
对象推入栈中, 开启了调度。(如果这里是第一个更新的话的这里还会判断变更前后值是否一致, 一致的话直接不开启调度)
scheduleUpdateOnFiber
这是其实就是开启调度渲染的过程了, 可以看React源码阅读(二)- Scheduler 。 入口函数就是scheduleUpdateOnFiber
。感兴趣的可以看看, 这一篇就不会再深究了。 直接聚焦到我们感兴趣的地方
因为整体逻辑较多, 所以我们直接定位两个地方。 既然开启了调度渲染的过程。 那么就会进入render
阶段。 感兴趣的可以看React源码阅读(三)- render。 主体逻辑都在这里了。
-
前面将
update
推入栈, 那么什么时候将他取出来用呢 ----> 可以找到我们render
阶段的入口函数renderRootSync
, 在一开始就调用了prepareFreshStack
。 该函数又调用了finishQueueingConcurrentUpdates
去处理栈中的update
对象 -
进入了
render
阶段。 那么就会接着再走到我们的beginWork
->renderWithHooks
, 就会再调用一次组件函数。 那么此时调用函数中的useState
又会发生什么呢?--------->首先要知道我们之前renderWithHooks
的时候讲过, 如果是更新阶段的话会挂载上HooksDispatcherOnUpdate
。 那么此时我们调用useState
实际上就是调用对应的updateState
。我们快速定位断点
以下按照上述两点展开
finishQueueingConcurrentUpdates
这个函数的逻辑很清晰。 按照我们入栈的方式, 将update
对象, queue
对象等从栈中取出来。 然后接着做和enqueueRenderPhaseUpdate
一样的操作, 就是将update
对象加入queue
的pending
链表
所以我们如果有State
的变更, 就会产生对应的update
对象。 最后的归宿都是加入的hook.queue.pending
链表上 。 isRenderPhaseUpdate
影响的是他加入的时机。
js
export function finishQueueingConcurrentUpdates(): void {
const endIndex = concurrentQueuesIndex;
concurrentQueuesIndex = 0;
concurrentlyUpdatedLanes = NoLanes;
let i = 0; // 下标从0开始
while (i < endIndex) {
---- 这个过程就是拿到栈中保存的东西而已-----------
const fiber: Fiber = concurrentQueues[i];
concurrentQueues[i++] = null;
const queue: ConcurrentQueue = concurrentQueues[i];
concurrentQueues[i++] = null;
const update: ConcurrentUpdate = concurrentQueues[i];
concurrentQueues[i++] = null;
const lane: Lane = concurrentQueues[i];
concurrentQueues[i++] = null;
---这里的逻辑和上面enqueueRenderPhaseUpdate的逻辑是一模一样的,都是放入链表的操作----
if (queue !== null && update !== null) {
const pending = queue.pending; // 又是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;
}
....
}
}
updateState/updateReducer
可以看到updateState
后直接就是调用updateReducer
, 所以我们直接走入updateReducer
这里的逻辑看起来也是很长, 我们逐个突破。 这里分成了几部分
第一部分就收首先先拿到hook
对象, 然后根据pendingQueue
和baseQueue
拿到我们的baseQueue
js
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
// 拿到当前的hook对象
// 该hook对象可能是根据current树的hook生成的, 也可能是直接复用给的。
// 具体可以看下面updateWorkInProgressHook内部实现
const hook = updateWorkInProgressHook();
const queue = hook.queue;
// 这里的赋值对useState来说都一样
// 因为本身我们给queue.lastRenderedReducerc初始化就是basicStateReducer
// 这里传入的reducer也是basicStateReducer
queue.lastRenderedReducer = reducer;
// currentHook在updateWorkInProgressHook内对他进行赋值了
// 拿到的就是current树上的hook链表
const current: Hook = (currentHook: any);
let baseQueue = current.baseQueue;
// 这里可以看enqueueRenderPhaseUpdate和finishQueueingConcurrentUpdates
// queue.pending上存放的就是对应的update对象
const pendingQueue = queue.pending;
if (pendingQueue !== null) {
if (baseQueue !== null) { // 初始化的时候还是null
const baseFirst = baseQueue.next;
const pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
// 当baseQueue为null的处理
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
.....
}
baseQueue
为空的处理如下。我们假设pendignQueues
上链接了四个update
对象。其实就是将pendingQueue
移到baseQueue
baseQueue
不为空的处理如下。 就是将pendingQueue
和baseQueue
拼接起来
这一部分就是遍历我们的baseQueue
, 根据update
上的action
, 然后拿到的最后的State
, 并更新到hook
对象上。 这里对代码进行了删减。 关注核心步骤即可
js
if (baseQueue !== null) {
const first = baseQueue.next;
let newState = current.baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let update = first; // 指针指向baseQueue.next。 其实就是第一个update
// 走一遍update链。 把update上的action都执行完毕, 计算出最后的值, 更新的hook中
do {
// 如果优先级不够的话会被打断的。 这里不扩展太多了
const shouldSkipUpdate = isHiddenUpdate
? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
: !isSubsetOfLanes(renderLanes, updateLane);
if (shouldSkipUpdate) {
....这里直接跳过了
} else {
....
// 我们在dispatchSetState的时候处理了hasEagerState, 具体可以回看
// 值已经算好了
if (update.hasEagerState) {
// 那么直接赋值
newState = ((update.eagerState: any): S);
} else {
// 还没算的话就从update对象中拿到action。 然后那个期待值
// 进行赋值
const action = update.action;
newState = reducer(newState, action);
}
}
update = update.next;
} while (update !== null && update !== first);
....
// 还是比较前后的State, 如果一致的话就设置ReceivedUpdate,后续进入Bailout策略
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
// 处理完之后将新的State挂上hook
hook.memoizedState = newState;
// 重置baseState和baseQueue
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
if (baseQueue === null) {
// `queue.lanes` is used for entangling transitions. We can set it back to
// zero once the queue is empty.
queue.lanes = NoLanes;
}
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
updateWorkInProgressHook
这里的逻辑虽然长, 但是前面理解过mountWorkInProgressHook
, 再来看这个其实也很好理解。 我分成了四个模块
js
function updateWorkInProgressHook(): Hook {
let nextCurrentHook: null | Hook;
// currentHook是全局维护的变量来的, 初始化为null
-----
这个逻辑块处理nextCurrentHook的初始化, 找的是current树
if (currentHook === null) {
// 拿到当前的页面使用的Fiber结构
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
// 拿到当前页面使用的Fiber结构的memoizedState。
// 上面存放的东西正是我们mountState挂上去的
nextCurrentHook = current.memoizedState;
} else {
// 如果是首次渲染的话, 此时初始化为null
nextCurrentHook = null;
}
} else {
// 如果currentHook不为空的话则指向他的next
nextCurrentHook = currentHook.next;
}
---------------
这个逻辑课处理的是nextWorkInProgressHook的初始化, 找的是workInProgress树
let nextWorkInProgressHook: null | Hook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
--------------
这里的逻辑块是有可复用的hook, 直接复用即可
// 当前已经有hook在workInProgress hook上已经有了, 直接复用
if (nextWorkInProgressHook !== null) {
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
--------------------------
这里的逻辑块是没有可复用的, 故根据current树上的hook链表生成自己的hook, 并挂上链表
// 如果没有nextWorkInProgressHook又没有nextCurrentHook可以直接抛出错误了
// 因为此时不应该会触发update
if (nextCurrentHook === null) {
throw new Error('Rendered more hooks than during the previous render.');
}
currentHook = nextCurrentHook;
// 生成新的hook, 用current hook的值进行初始化
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
// workInProgressHook还是跟mountWorkInProgressHook中一样, 始终保持指向最后的节点。
// 如果为空的话说明还没开启挂到memoizedState, 故需要做初始化
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
// 不为空的话让新的hook继续挂上链表,让workInProgressHook指向他的next即可
workInProgressHook = workInProgressHook.next = newHook;
}
}
------------------
return workInProgressHook;
}
小总结
我们依赖的是current
树上的Fiber
的memoizedState
上的hook
对象去生成workInProgress
树上的Fiber
的hook
对象。 在这个过程中使用workInProgressHook
当做指针。 在处理每一个hook
对象的时候。 就会找到hook
的pendingQueue
(里面存放着dispatchSetstate
触发存入的update
对象)。 然后遍历, 根据update
上的action
获取期待值。 再更新到hook
上
三、流程图示
我们来一段代码, 然后走一遍流程。 如下图所示有两个useState
的用法。 然后写了个点击函数触发了他们更新
js
function App() {
const [count, setCount] = useState(1);
const [count1 ,setCount1] = useState(() => 3);
return (
<div>
<div onClick={() => {
setCount(count + 1);
setCount(count + 2);
setCount(count => count + 3);
setCount1(count1 + 1);
}}>{count}</div>
</div>
);
}
- 首先进入页面的时候会进入初次挂载阶段。 在调用该
APP()
函数的时候, 会顺着useState
的顺序形成对应的hook
链表(通过mountState
函数)。 此时生成的Fiber
如图所示。 构造完render
后进入commit
阶段后相关页面就显示了
- 接着当我们点击
onClick
挨个触发setCount
的时候, 传入了action
, 会根据action
等生成update
对象。 然后将queue
,update
等推入concurrentQueues
(通过dispatchSetState
)。 这里一共生成了四个update
对象。 - 接着开启调度过程(通过
scheduleUpdateOnFiber
), 会再次走入render
阶段。 然后处理concurrentQueues
, 将update
对象放入到queue
的pending
链表中。 此时Fiber
结构长这样
要注意因为我们dispatchSetState
传入的是初次渲染的queue
, 故其实我们的操作响应的是current Fiber
, 而不是此时render
生成的workInProgress
- 既然再次走入了
render
过程, 此时又会调用该组件函数, 那么此时调用useState
。 此时Fiber
节点的初始情况如下
- 此时调用第一个
useState
的时候, 走入了updateWorkInProgressHook
。 此时nextCurrentHook
指向current Fiber
的第一个hook
。nextWorkInProgressHook
指向workInProgress Fiber
的第一个hook
。
- 诶发现
nextWorkInProgressHook
为空, 那么此时我就根据nextCurrentHook
去生成自己的hook
对象(唯一不同的是此时next
为空)。并且挂上去
- 此时
workInProgressHook
指向自己的hook
对象了, 此时一看发现我的queue.pending
不为空。那不行,我得处理掉他。 首先他是先把pendingQueue
直接移到baseQueue
。 然后再去处理baseQueue
。 这里就是指向的转移而已, 我直接画图
- 接着就是来处理
update
对象了, 我们直接聚焦到处理过程就好。 就是通过遍历update
链表,更新state
。 这里用newState
来保持state
的上下文 - 拿到最新的
state
之后去更新hook
即可。 至此,我们对一个useState
的处理就结束了。 这里处理完之后呢。 会继续按顺序调用函数的下一个useState
, 此时继续走一样的路, 还是根据current Fiber
生成新的hook
, 然后挂上workInProgress
的hook
链, 让workInProgressHook
指向他。 之后都是一样的流程了
至此图示部分就结束了。当然,看源码就知道还有其他情况需要考虑, 这里的图示就讲了主逻辑大概是怎么走的
看懂了你就能理解
- 为什么函数组件能保持状态
hook
为什么不能写在判断,循环语句内- 调用了
Set
函数但是state
没有变化的情况下为什么函数不会再次被调用 useState
传参为函数时是如何处理的Set
函数传参使用函数为什么能拿到上一次变更的state
- .....