scheduleWork
1 ) 概述
-
在
ReactDOM.render
,setState
,forceUpdate
这几个方法最终都调用了scheduleWork
这个方法 -
在
scheduleWork
当中,它需要去找到更新对应的FiberRoot
节点- 在使用
ReactDOM.render
的时候,传给scheduleWork
的就是FiberRoot
节点 - 在使用
setState
,forceUpdate
的时候,传过去的都是某一个组件对应的 fiber 节点,而不是 FiberRoot - 这时候需要找到
FiberRoot
节点
- 在使用
-
如果符合条件需要重置 stack
- 这个 stack 里面有一些公共的变量
- 这些公共变量在后续的调度和更新的过程中都非常的重要
- 如果符合一定的条件,要重置这个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
- 在上面这个 fiber tree 结构的 demo示例中,点击了最后这个 button 节点
- 实际调用 setState 是我们写的这个 List 组件
- 最终是把 RootFiber 这个 fiber 节点加入到调度队列当中
- 而不是直接把我们的 List 它对应的 fiber 对象加入到调度队列当中
- 每一次进入到调度队列的, 都是一个 RootFiber 这个对象, 不会是其他的
- 因为更新的开始,也是从 RootFiber 开始的
2 )源码文章来源:https://www.toymoban.com/news/detail-787888.html
定位到 packages/react-reconciler/src/ReactFiberScheduler.js文章来源地址https://www.toymoban.com/news/detail-787888.html
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
- 第一个是 fiber对象
- 第二个是 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当中的用来记录更新流程当中的时间 - 这里涉及的东西比较多,先跳过,涉及一个独立的模块
- DEV 的判断也跳过
- 在这个方法中
- 根据传入进来的 fiber 节点,它要去向上寻找,找到对应的 RootFiber 对象
- 找的过程当中,进行一系列的操作
-
if (fiber.expirationTime === NoWork || fiber.expirationTime > expirationTime)
- 当这个节点没有任何更新(没有设置过过期时间) 或者 已有更新产生未完成的任务的优先级 低于 当前的更新
- 则将本身设置成优先级更高的 expirationTime
- 这里主要任务是更新优先级
- 第二步获取
alternate
并进行判断if (alternate !== null && (alternate.expirationTime === NoWork || alternate.expirationTime > expirationTime))
- 这很明显就是用来更新
alternate
的 expirationTime - 它其实跟上面更新
fiber.expirationTime
是一样的
- 这很明显就是用来更新
- 接下去,往上一层去找,比如 List 这个组件的 return 属性是 上一层的 div 组件
- 进入判断
if (node === null && fiber.tag === HostRoot)
- 注意,只有
RootFiber.return === null
, 其他的都有上级Fiber - 当
node === null
时,当前 fiber 就是 RootFiber, 同时,HostRoot
代表的就是RootFiber
- 这时候,
root = fiber.stateNode
- 注意,只有
- 如果不满足上述判断,则进入循环处理
- whle循环,向上查找
-
node.childExpirationTime
是否是子树中优先级最高的 - 如果不是高优先级,则更新成高优先级的
- 同样对
alternate.childExpirationTime
的判断也是同样的更新 - 接着,对
node.return
和node.tag
的判断是否是 RootFiber, 赋值 root - 如果不是,则
node = node.return
向上查找
-
- whle循环,向上查找
- 如果
root === null
的时候,则return null
- 下面的
if (enableSchedulerracing)
不涉及主流程,跳过 - 最终 return root, 把 FiberRoot 返回
- 上面首先调用了
- 获取 root 后,如果 root 是 null,则 return
- 接着
if(!isWorking && nextRenderExpiration !== NoWork && expirationTime < nextRenderExpirationTime)
- 如果没有在渲染更新的任务,并且,任务是异步任务没有执行完,并且新任务的优先级高于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
- 当没有正在渲染更新,或正在提交,或不是 本root 节点(注:是针对多应用而言的,单应用只有一个root,这里很少会匹配)
- 注意,isWorking 会包含 committing
- committing 是第二阶段,是不可打断的阶段,把fiber树渲染完成后,更新到dom上的这个过程是 commiting 阶段
- isCommitting 只有在中间执行的过程中才会是 true, 其他才会是false, 可以查看
commitRoot
函数
- 符合条件,赋值
rootExpirationTime
并调用 requestWork 函数- 为何要重新读取 expirationTime, 因为之前调用过
markPendingPriorityLevel
可能导致 - root.epirationTime 不一定和传入的 expirationTime 是一样的
- 为何要重新读取 expirationTime, 因为之前调用过
- 当没有正在渲染更新,或正在提交,或不是 本root 节点(注:是针对多应用而言的,单应用只有一个root,这里很少会匹配)
- 最后是涉及到 nestUpdateCount 的判断
- 用来防止在组件更新的流程中,比如在 componentDidMount 中再次修改状态
- 导致又进行了一次更新, 导致无限循环的处理,这里进行提醒
到了这里,关于React16源码: React中调度之scheduleWork的源码实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!