1 ) 概述
在 ReactDOM.render
, setState
, forceUpdate
这几个方法最终都调用了 scheduleWork
这个方法
在 scheduleWork
当中,它需要去找到更新对应的 FiberRoot
节点
ReactDOM.render
的时候,传给 scheduleWork
的就是 FiberRoot
节点setState
, forceUpdate
的时候,传过去的都是某一个组件对应的 fiber 节点,而不是 FiberRootFiberRoot
节点如果符合条件需要重置 stack
如果符合条件就去请求工作调度
拿之前的一个 Fiber Tree 节点来举例
-----current----->
FiberRoot RootFiber
<---stateNode----- |
|
child
|
↓
App
|
child
|
↓
|
div
/ \
/ \
child child
/ \
/ \
/ \
Input-sibling-> List
| \
child child
| \
↓ \
input span --sibling--> span --sibling--> span --sibling--> button
2 )源码
定位到 packages/react-reconciler/src/ReactFiberScheduler.js
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
const root = scheduleWorkToRoot(fiber, expirationTime);
if (root === null) {
return;
}
if (
!isWorking &&
nextRenderExpirationTime !== NoWork &&
expirationTime < nextRenderExpirationTime
) {
// This is an interruption. (Used for performance tracking.)
interruptedBy = fiber;
resetStack();
}
markPendingPriorityLevel(root, expirationTime);
if (
// If we're in the render phase, we don't need to schedule this root
// for an update, because we'll do it before we exit...
!isWorking ||
isCommitting ||
// ...unless this is a different root than the one we're rendering.
nextRoot !== root
) {
const rootExpirationTime = root.expirationTime;
requestWork(root, rootExpirationTime);
}
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
// Reset this back to zero so subsequent updates don't throw.
nestedUpdateCount = 0;
invariant(
false,
'Maximum update depth exceeded. This can happen when a ' +
'component repeatedly calls setState inside ' +
'componentWillUpdate or componentDidUpdate. React limits ' +
'the number of nested updates to prevent infinite loops.',
);
}
}
fiber: Fiber, expirationTime: ExpirationTime
scheduleWorkToRoot
function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
recordScheduleUpdate();
if (__DEV__) {
if (fiber.tag === ClassComponent) {
const instance = fiber.stateNode;
warnAboutInvalidUpdates(instance);
}
}
// Update the source fiber's expiration time
if (
fiber.expirationTime === NoWork ||
fiber.expirationTime > expirationTime
) {
fiber.expirationTime = expirationTime;
}
let alternate = fiber.alternate;
if (
alternate !== null &&
(alternate.expirationTime === NoWork ||
alternate.expirationTime > expirationTime)
) {
alternate.expirationTime = expirationTime;
}
// Walk the parent path to the root and update the child expiration time.
let node = fiber.return;
let root = null;
if (node === null && fiber.tag === HostRoot) {
root = fiber.stateNode;
} else {
while (node !== null) {
alternate = node.alternate;
if (
node.childExpirationTime === NoWork ||
node.childExpirationTime > expirationTime
) {
node.childExpirationTime = expirationTime;
if (
alternate !== null &&
(alternate.childExpirationTime === NoWork ||
alternate.childExpirationTime > expirationTime)
) {
alternate.childExpirationTime = expirationTime;
}
} else if (
alternate !== null &&
(alternate.childExpirationTime === NoWork ||
alternate.childExpirationTime > expirationTime)
) {
alternate.childExpirationTime = expirationTime;
}
if (node.return === null && node.tag === HostRoot) {
root = node.stateNode;
break;
}
node = node.return;
}
}
if (root === null) {
if (__DEV__ && fiber.tag === ClassComponent) {
warnAboutUpdateOnUnmounted(fiber);
}
return null;
}
if (enableSchedulerTracing) {
const interactions = __interactionsRef.current;
if (interactions.size > 0) {
const pendingInteractionMap = root.pendingInteractionMap;
const pendingInteractions = pendingInteractionMap.get(expirationTime);
if (pendingInteractions != null) {
interactions.forEach(interaction => {
if (!pendingInteractions.has(interaction)) {
// Update the pending async work count for previously unscheduled interaction.
interaction.__count++;
}
pendingInteractions.add(interaction);
});
} else {
pendingInteractionMap.set(expirationTime, new Set(interactions));
// Update the pending async work count for the current interactions.
interactions.forEach(interaction => {
interaction.__count++;
});
}
const subscriber = __subscriberRef.current;
if (subscriber !== null) {
const threadID = computeThreadID(
expirationTime,
root.interactionThreadID,
);
subscriber.onWorkScheduled(interactions, threadID);
}
}
}
return root;
}
recordScheduleUpdate
这个方法是react当中的用来记录更新流程当中的时间if (fiber.expirationTime === NoWork || fiber.expirationTime > expirationTime)
alternate
并进行判断 if (alternate !== null && (alternate.expirationTime === NoWork || alternate.expirationTime > expirationTime))
alternate
的 expirationTimefiber.expirationTime
是一样的if (node === null && fiber.tag === HostRoot)
RootFiber.return === null
, 其他的都有上级Fibernode === null
时,当前 fiber 就是 RootFiber, 同时,HostRoot
代表的就是 RootFiber
root = fiber.stateNode
node.childExpirationTime
是否是子树中优先级最高的alternate.childExpirationTime
的判断也是同样的更新node.return
和 node.tag
的判断是否是 RootFiber, 赋值 rootnode = node.return
向上查找root === null
的时候,则 return null
if (enableSchedulerracing)
不涉及主流程,跳过if(!isWorking && nextRenderExpiration !== NoWork && expirationTime < nextRenderExpirationTime)
interruptedBy
是一个被用来记录在哪里打断的变量值,这里可以不用太关注resetStack()
function resetStack() {
// nextUnitOfWork 用来记录遍历整个子树的时候,执行到了哪个节点的更新, 即下一个即将要更新的节点
// 这个判断如果是 true 则代表,之前的更新的是一个异步的任务,执行到一部分,由于时间片不够,把执行权交给浏览器
// 这个值记录了下一个要执行的节点,如果现在没有优先级任务中断,它会回来继续执行 nextUnitOfWork 的任务更新
if (nextUnitOfWork !== null) {
// 指向上级,向上查找
let interruptedWork = nextUnitOfWork.return;
// 不断向上找被打断的任务,执行 unwindInterruptedWork
while (interruptedWork !== null) {
// 有了更高优先级的任务进来了,要进行更新,还是要从头开始更新,有存在的 nextUnitOfWork
// 说明上层的几个组件 可能已经进行过更新了, 新的任务进来,再从头进行更新可能会导致前后的state并非这次想要更新的state
// 会导致 state 错乱,在这里有 nextUnitOfWork 的情况,要把之前已经更新过的节点(上一个优先级任务对应的节点) 进行状态退回
// 把状态退回到没有更新过的状态,再去执行优先级更高的任务,这块具体的先跳过
unwindInterruptedWork(interruptedWork);
interruptedWork = interruptedWork.return;
}
}
if (__DEV__) {
ReactStrictModeWarnings.discardPendingWarnings();
checkThatStackIsEmpty();
}
// 设置公共变量的重置
nextRoot = null;
nextRenderExpirationTime = NoWork;
nextLatestAbsoluteTimeoutMs = -1;
nextRenderDidError = false;
nextUnitOfWork = null;
}
markPendingPriorityLevel
if(!isWorking || isCommitting || nextRoot !== root)
是否要 requestWork
commitRoot
函数rootExpirationTime
并调用 requestWork 函数
markPendingPriorityLevel
可能导致