共计 9653 个字符,预计需要花费 25 分钟才能阅读完成。
react 源码解析 12. 状态更新流程
视频解说(高效学习):进入学习
往期文章:
1. 开篇介绍和面试题
2.react 的设计理念
3.react 源码架构
4. 源码目录构造和调试
5.jsx& 外围 api
6.legacy 和 concurrent 模式入口函数
7.Fiber 架构
8.render 阶段
9.diff 算法
10.commit 阶段
11. 生命周期
12. 状态更新流程
13.hooks 源码
14. 手写 hooks
15.scheduler&Lane
16.concurrent 模式
17.context
18 事件零碎
19. 手写迷你版 react
20. 总结 & 第一章的面试题解答
21.demo
setState&forceUpdate
在 react 中触发状态更新的几种形式:
- ReactDOM.render
- this.setState
- this.forceUpdate
- useState
- useReducer
咱们重点看下重点看下 this.setState 和 this.forceUpdate,hook 在第 13 章讲
-
this.setState 内调用 this.updater.enqueueSetState,次要是将 update 退出 updateQueue 中
//ReactBaseClasses.js Component.prototype.setState = function (partialState, callback) {if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) { {throw Error( "setState(...): takes an object of state variables to update or a function which returns an object of state variables." ); } } this.updater.enqueueSetState(this, partialState, callback, 'setState'); };
//ReactFiberClassComponent.old.js | |
enqueueSetState(inst, payload, callback) {const fiber = getInstance(inst);//fiber 实例 | |
const eventTime = requestEventTime(); | |
const suspenseConfig = requestCurrentSuspenseConfig(); | |
const lane = requestUpdateLane(fiber, suspenseConfig);// 优先级 | |
const update = createUpdate(eventTime, lane, suspenseConfig);// 创立 update | |
update.payload = payload; | |
if (callback !== undefined && callback !== null) { // 赋值回调 | |
update.callback = callback; | |
} | |
enqueueUpdate(fiber, update);//update 退出 updateQueue | |
scheduleUpdateOnFiber(fiber, lane, eventTime);// 调度 update | |
} |
enqueueUpdate 用来将 update 退出 updateQueue 队列
//ReactUpdateQueue.old.js | |
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) { | |
const updateQueue = fiber.updateQueue; | |
if (updateQueue === null) {return;} | |
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared; | |
const pending = sharedQueue.pending; | |
if (pending === null) {update.next = update;// 与本人造成环状链表} else { | |
update.next = pending.next;// 退出链表的结尾 | |
pending.next = update; | |
} | |
sharedQueue.pending = update; | |
} |
-
this.forceUpdate 和 this.setState 一样,只是会让 tag 赋值 ForceUpdate
//ReactBaseClasses.js Component.prototype.forceUpdate = function(callback) {this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); };
//ReactFiberClassComponent.old.js | |
enqueueForceUpdate(inst, callback) {const fiber = getInstance(inst); | |
const eventTime = requestEventTime(); | |
const suspenseConfig = requestCurrentSuspenseConfig(); | |
const lane = requestUpdateLane(fiber, suspenseConfig); | |
const update = createUpdate(eventTime, lane, suspenseConfig); | |
//tag 赋值 ForceUpdate | |
update.tag = ForceUpdate; | |
if (callback !== undefined && callback !== null) {update.callback = callback;} | |
enqueueUpdate(fiber, update); | |
scheduleUpdateOnFiber(fiber, lane, eventTime); | |
}, | |
}; |
如果标记 ForceUpdate,render 阶段组件更新会依据 checkHasForceUpdateAfterProcessing,和 checkShouldComponentUpdate 来判断,如果 Update 的 tag 是 ForceUpdate,则 checkHasForceUpdateAfterProcessing 为 true,当组件是 PureComponent 时,checkShouldComponentUpdate 会浅比拟 state 和 props,所以当应用 this.forceUpdate 肯定会更新
//ReactFiberClassComponent.old.js | |
const shouldUpdate = | |
checkHasForceUpdateAfterProcessing() || | |
checkShouldComponentUpdate( | |
workInProgress, | |
ctor, | |
oldProps, | |
newProps, | |
oldState, | |
newState, | |
nextContext, | |
); |
状态更新整体流程
Update&updateQueue
HostRoot 或者 ClassComponent 触发更新后,会在函数 createUpdate 中创立 update,并在前面的 render 阶段的 beginWork 中计算 Update。FunctionComponent 对应的 Update 在第 11 章讲,它和 HostRoot 或者 ClassComponent 的 Update 构造有些不一样
//ReactUpdateQueue.old.js | |
export function createUpdate(eventTime: number, lane: Lane): Update<*> {// 创立 update | |
const update: Update<*> = { | |
eventTime, | |
lane, | |
tag: UpdateState, | |
payload: null, | |
callback: null, | |
next: null, | |
}; | |
return update; | |
} |
咱们次要关注这些参数:
- lane:优先级(第 12 章讲)
- tag:更新的类型,例如 UpdateState、ReplaceState
- payload:ClassComponent 的 payload 是 setState 第一个参数,HostRoot 的 payload 是 ReactDOM.render 的第一个参数
- callback:setState 的第二个参数
- next:连贯下一个 Update 造成一个链表,例如同时触发多个 setState 时会造成多个 Update,而后用 next 连贯
对于 HostRoot 或者 ClassComponent 会在 mount 的时候应用 initializeUpdateQueue 创立 updateQueue,而后将 updateQueue 挂载到 fiber 节点上
//ReactUpdateQueue.old.js | |
export function initializeUpdateQueue<State>(fiber: Fiber): void { | |
const queue: UpdateQueue<State> = { | |
baseState: fiber.memoizedState, | |
firstBaseUpdate: null, | |
lastBaseUpdate: null, | |
shared: {pending: null,}, | |
effects: null, | |
}; | |
fiber.updateQueue = queue; | |
} |
- baseState:初始 state,前面会基于这个 state,依据 Update 计算新的 state
- firstBaseUpdate、lastBaseUpdate:Update 造成的链表的头和尾
- shared.pending:新产生的 update 会以单向环状链表保留在 shared.pending 上,计算 state 的时候会剪开这个环状链表,并且链接在 lastBaseUpdate 后
- effects:calback 不为 null 的 update
从触发更新的 fiber 节点向上遍历到 rootFiber
在 markUpdateLaneFromFiberToRoot 函数中会从触发更新的节点开始向上遍历到 rootFiber,遍历的过程会解决节点的优先级(第 15 章讲)
//ReactFiberWorkLoop.old.js | |
function markUpdateLaneFromFiberToRoot( | |
sourceFiber: Fiber, | |
lane: Lane, | |
): FiberRoot | null {sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); | |
let alternate = sourceFiber.alternate; | |
if (alternate !== null) {alternate.lanes = mergeLanes(alternate.lanes, lane); | |
} | |
let node = sourceFiber; | |
let parent = sourceFiber.return; | |
while (parent !== null) {// 从触发更新的节点开始向上遍历到 rootFiber | |
parent.childLanes = mergeLanes(parent.childLanes, lane);// 合并 childLanes 优先级 | |
alternate = parent.alternate; | |
if (alternate !== null) {alternate.childLanes = mergeLanes(alternate.childLanes, lane); | |
} else { } | |
node = parent; | |
parent = parent.return; | |
} | |
if (node.tag === HostRoot) { | |
const root: FiberRoot = node.stateNode; | |
return root; | |
} else {return null;} | |
} |
例如 B 节点触发更新,B 节点被被标记为 normal 的 update,也就是图中的 u1,而后向上遍历到根节点,在根节点上打上一个 normal 的 update,如果此时 B 节点又触发了一个 userBlocking 的 Update,同样会向上遍历到根节点,在根节点上打上一个 userBlocking 的 update。
如果以后根节点更新的优先级是 normal,u1、u2 都参加状态的计算,如果以后根节点更新的优先级是 userBlocking,则只有 u2 参加计算
调度
在 ensureRootIsScheduled 中,scheduleCallback 会以一个优先级调度 render 阶段的开始函数 performSyncWorkOnRoot 或者 performConcurrentWorkOnRoot
//ReactFiberWorkLoop.old.js | |
if (newCallbackPriority === SyncLanePriority) { | |
// 工作曾经过期,须要同步执行 render 阶段 | |
newCallbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root) | |
); | |
} else { | |
// 依据工作优先级异步执行 render 阶段 | |
var schedulerPriorityLevel = lanePriorityToSchedulerPriority(newCallbackPriority); | |
newCallbackNode = scheduleCallback( | |
schedulerPriorityLevel, | |
performConcurrentWorkOnRoot.bind(null, root) | |
); | |
} |
状态更新
classComponent 状态计算产生在 processUpdateQueue 函数中,波及很多链表操作,看图更加直白
- 初始时 fiber.updateQueue 单链表上有 firstBaseUpdate(update1)和 lastBaseUpdate(update2),以 next 连贯
- fiber.updateQueue.shared 环状链表上有 update3 和 update4,以 next 连贯相互连贯
- 计算 state 时,先将 fiber.updateQueue.shared 环状链表‘剪开’,造成单链表,连贯在 fiber.updateQueue 前面造成 baseUpdate
-
而后遍历按这条链表,依据 baseState 计算出 memoizedState
带优先级的状态更新
相似 git 提交,这里的 c3 意味着高优先级的工作,比方用户登程的事件,数据申请,同步执行的代码等。
- 通过 ReactDOM.render 创立的利用没有优先级的概念,类比 git 提交,相当于先 commit,而后提交 c3
-
在 concurrent 模式下,相似 git rebase,先暂存之前的代码,在 master 上开发,而后 rebase 到之前的分支上
优先级是由 Scheduler 来调度的,这里咱们只关怀状态计算时的优先级排序,也就是在函数 processUpdateQueue 中产生的计算,例如初始时有 c1-c4 四个 update,其中 c1 和 c3 为高优先级
- 在第一次 render 的时候,低优先级的 update 会跳过,所以只有 c1 和 c3 退出状态的计算
- 在第二次 render 的时候,会以第一次中跳过的 update(c2)之前的 update(c1)作为 baseState,跳过的 update 和之后的 update(c2,c3,c4)作为 baseUpdate 从新计算
在在 concurrent 模式下,componentWillMount 可能会执行屡次,变现和之前的版本不统一
留神,fiber.updateQueue.shared 会同时存在于 workInprogress Fiber 和 current Fiber,目标是为了避免高优先级打断正在进行的计算而导致状态失落,这段代码也是产生在 processUpdateQueue 中
看 demo_8 的优先级
当初来看下计算状态的函数
//ReactUpdateQueue.old.js | |
export function processUpdateQueue<State>( | |
workInProgress: Fiber, | |
props: any, | |
instance: any, | |
renderLanes: Lanes, | |
): void {const queue: UpdateQueue<State> = (workInProgress.updateQueue: any); | |
hasForceUpdate = false; | |
let firstBaseUpdate = queue.firstBaseUpdate;//updateQueue 的第一个 Update | |
let lastBaseUpdate = queue.lastBaseUpdate;//updateQueue 的最初一个 Update | |
let pendingQueue = queue.shared.pending;// 未计算的 pendingQueue | |
if (pendingQueue !== null) { | |
queue.shared.pending = null; | |
const lastPendingUpdate = pendingQueue;// 未计算的 ppendingQueue 的最初一个 update | |
const firstPendingUpdate = lastPendingUpdate.next;// 未计算的 pendingQueue 的第一个 update | |
lastPendingUpdate.next = null;// 剪开环状链表 | |
if (lastBaseUpdate === null) {// 将 pendingQueue 退出到 updateQueue | |
firstBaseUpdate = firstPendingUpdate; | |
} else {lastBaseUpdate.next = firstPendingUpdate;} | |
lastBaseUpdate = lastPendingUpdate; | |
const current = workInProgress.alternate;//current 上做同样的操作 | |
if (current !== null) {const currentQueue: UpdateQueue<State> = (current.updateQueue: any); | |
const currentLastBaseUpdate = currentQueue.lastBaseUpdate; | |
if (currentLastBaseUpdate !== lastBaseUpdate) {if (currentLastBaseUpdate === null) {currentQueue.firstBaseUpdate = firstPendingUpdate;} else {currentLastBaseUpdate.next = firstPendingUpdate;} | |
currentQueue.lastBaseUpdate = lastPendingUpdate; | |
} | |
} | |
} | |
if (firstBaseUpdate !== null) { | |
let newState = queue.baseState; | |
let newLanes = NoLanes; | |
let newBaseState = null; | |
let newFirstBaseUpdate = null; | |
let newLastBaseUpdate = null; | |
let update = firstBaseUpdate; | |
do { | |
const updateLane = update.lane; | |
const updateEventTime = update.eventTime; | |
if (!isSubsetOfLanes(renderLanes, updateLane)) {// 判断优先级是够足够 | |
const clone: Update<State> = {// 优先级不够 跳过以后 update | |
eventTime: updateEventTime, | |
lane: updateLane, | |
tag: update.tag, | |
payload: update.payload, | |
callback: update.callback, | |
next: null, | |
}; | |
if (newLastBaseUpdate === null) {// 保留跳过的 update | |
newFirstBaseUpdate = newLastBaseUpdate = clone; | |
newBaseState = newState; | |
} else {newLastBaseUpdate = newLastBaseUpdate.next = clone;} | |
newLanes = mergeLanes(newLanes, updateLane); | |
} else { | |
// 直到 newLastBaseUpdate 为 null 才不会计算,避免 updateQueue 没计算完 | |
if (newLastBaseUpdate !== null) { | |
const clone: Update<State> = { | |
eventTime: updateEventTime, | |
lane: NoLane, | |
tag: update.tag, | |
payload: update.payload, | |
callback: update.callback, | |
next: null, | |
}; | |
newLastBaseUpdate = newLastBaseUpdate.next = clone; | |
} | |
newState = getStateFromUpdate(// 依据 updateQueue 计算 state | |
workInProgress, | |
queue, | |
update, | |
newState, | |
props, | |
instance, | |
); | |
const callback = update.callback; | |
if (callback !== null) { | |
workInProgress.flags |= Callback;//Callback flag | |
const effects = queue.effects; | |
if (effects === null) {queue.effects = [update]; | |
} else {effects.push(update); | |
} | |
} | |
} | |
update = update.next;// 下一个 update | |
if (update === null) {// 重置 updateQueue | |
pendingQueue = queue.shared.pending; | |
if (pendingQueue === null) {break;} else { | |
const lastPendingUpdate = pendingQueue; | |
const firstPendingUpdate = ((lastPendingUpdate.next: any): Update<State>); | |
lastPendingUpdate.next = null; | |
update = firstPendingUpdate; | |
queue.lastBaseUpdate = lastPendingUpdate; | |
queue.shared.pending = null; | |
} | |
} | |
} while (true); | |
if (newLastBaseUpdate === null) {newBaseState = newState;} | |
queue.baseState = ((newBaseState: any): State);// 新的 state | |
queue.firstBaseUpdate = newFirstBaseUpdate;// 新的第一个 update | |
queue.lastBaseUpdate = newLastBaseUpdate;// 新的最初一个 update | |
markSkippedUpdateLanes(newLanes); | |
workInProgress.lanes = newLanes; | |
workInProgress.memoizedState = newState; | |
} | |
//... | |
} |