从本篇开始,咱们就正式进入React的核心调度算法—Fiber调度机制。javascript
前言:
你须要知道:浅谈React 16中的Fiber机制、React源码解析之RootFiber、React源码解析之FiberRootphp
在以前的文章中讲到,React更新的方式有三种:
(1)ReactDOM.render() || hydrate(ReactDOMServer渲染)
(2)setState()
(3)forceUpdate()java
在createUpdate
后就进入scheduleWork
流程,接下来咱们就正式进入调度流程node
1、scheduleUpdateOnFiber()
做用:
调度update
任务react
提示:scheduleWork
即scheduleUpdateOnFiber
:git
export const scheduleWork = scheduleUpdateOnFiber;
复制代码
源码:github
//scheduleWork
export function scheduleUpdateOnFiber(
fiber: Fiber,
expirationTime: ExpirationTime,
) {
//判断是不是无限循环update
checkForNestedUpdates();
//测试环境用的,不看
warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
//找到rootFiber并遍历更新子节点的expirationTime
const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
if (root === null) {
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
return;
}
//NoWork表示无更新操做
root.pingTime = NoWork;
//判断是否有高优先级任务打断当前正在执行的任务
checkForInterruption(fiber, expirationTime);
//报告调度更新,测试环境用的,可不看
recordScheduleUpdate();
// TODO: computeExpirationForFiber also reads the priority. Pass the
// priority as an argument to that function and this one.
const priorityLevel = getCurrentPriorityLevel();
//1073741823
//若是expirationTime等于最大整型值的话
//若是是同步任务的过时时间的话
if (expirationTime === Sync) {
//若是还未渲染,update是未分批次的,
//也就是第一次渲染前
if (
// Check if we're inside unbatchedUpdates
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// Check if we're not already rendering
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// Register pending interactions on the root to avoid losing traced interaction data.
//跟踪这些update,并计数、检测它们是否会报错
schedulePendingInteractions(root, expirationTime);
// This is a legacy edge case. The initial mount of a ReactDOM.render-ed
// root inside of batchedUpdates should be synchronous, but layout updates
// should be deferred until the end of the batch.
//批量更新时,render是要保持同步的,但布局的更新要延迟到批量更新的末尾才执行
//初始化root
//调用workLoop进行循环单元更新
let callback = renderRoot(root, Sync, true);
while (callback !== null) {
callback = callback(true);
}
}
//render后
else {
//当即执行调度任务
scheduleCallbackForRoot(root, ImmediatePriority, Sync);
//当前没有update时
if (executionContext === NoContext) {
// Flush the synchronous work now, wnless we're already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
// scheduleCallbackForFiber to preserve the ability to schedule a callback
// without immediately flushing it. We only do this for user-initated
// updates, to preserve historical behavior of sync mode.
//刷新同步任务队列
flushSyncCallbackQueue();
}
}
}
//若是是异步任务的话,则当即执行调度任务
else {
scheduleCallbackForRoot(root, priorityLevel, expirationTime);
}
if (
(executionContext & DiscreteEventContext) !== NoContext &&
// Only updates at user-blocking priority or greater are considered
// discrete, even inside a discrete event.
// 只有在用户阻止优先级或更高优先级的更新才被视为离散,即便在离散事件中也是如此
(priorityLevel === UserBlockingPriority ||
priorityLevel === ImmediatePriority)
) {
// This is the result of a discrete event. Track the lowest priority
// discrete update per root so we can flush them early, if needed.
//这是离散事件的结果。 跟踪每一个根的最低优先级离散更新,以便咱们能够在须要时尽早清除它们。
//若是rootsWithPendingDiscreteUpdates为null,则初始化它
if (rootsWithPendingDiscreteUpdates === null) {
//key是root,value是expirationTime
rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
} else {
//获取最新的DiscreteTime
const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
//更新DiscreteTime
if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
rootsWithPendingDiscreteUpdates.set(root, expirationTime);
}
}
}
}
复制代码
解析:
有点长,咱们慢慢看↓
web
2、checkForNestedUpdates()
做用:
判断是不是无限循环的update
算法
源码:sql
// Use these to prevent an infinite loop of nested updates
const NESTED_UPDATE_LIMIT = 50;
let nestedUpdateCount: number = 0;
//防止无限循环地嵌套更新
function checkForNestedUpdates() {
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
nestedUpdateCount = 0;
rootWithNestedUpdates = null;
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.',
);
}
}
复制代码
解析:
超过 50层嵌套update
,就终止进行调度,并报出error
常见的形成死循环的为两种状况:
① 在render()
中无条件调用setState()
注意:
有条件调用setState()
的话,是能够放在render()
中的
render(){
if(xxx){
this.setState({ yyy })
}
}
复制代码
② 以下图,在shouldComponentUpdate()
和componentWillUpdate()
中调用setState()
3、markUpdateTimeFromFiberToRoot()
做用:
找到rootFiber
并遍历更新子节点的expirationTime
源码:
//目标fiber会向上寻找rootFiber对象,在寻找的过程当中会进行一些操做
function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
// Update the source fiber's expiration time
//若是fiber对象的过时时间小于 expirationTime,则更新fiber对象的过时时间
//也就是说,当前fiber的优先级是小于expirationTime的优先级的,如今要调高fiber的优先级
if (fiber.expirationTime < expirationTime) {
fiber.expirationTime = expirationTime;
}
//在enqueueUpdate()中有讲到,与fiber.current是映射关系
let alternate = fiber.alternate;
//同上
if (alternate !== null && alternate.expirationTime < expirationTime) {
alternate.expirationTime = expirationTime;
}
// Walk the parent path to the root and update the child expiration time.
//向上遍历父节点,直到root节点,在遍历的过程当中更新子节点的expirationTime
//fiber的父节点
let node = fiber.return;
let root = null;
//node=null,表示是没有父节点了,也就是到达了RootFiber,即最大父节点
//HostRoot即树的顶端节点root
if (node === null && fiber.tag === HostRoot) {
//RootFiber的stateNode就是FiberRoot
root = fiber.stateNode;
}
//没有到达FiberRoot的话,则进行循环
else {
while (node !== null) {
alternate = node.alternate;
//若是父节点的全部子节点中优先级最高的更新时间仍小于expirationTime的话
//则提升优先级
if (node.childExpirationTime < expirationTime) {
//从新赋值
node.childExpirationTime = expirationTime;
//alternate是相对于fiber的另外一个对象,也要进行更新
if (
alternate !== null &&
alternate.childExpirationTime < expirationTime
) {
alternate.childExpirationTime = expirationTime;
}
}
//别看差了是对应(node.childExpirationTime < expirationTime)的if
else if (
alternate !== null &&
alternate.childExpirationTime < expirationTime
) {
alternate.childExpirationTime = expirationTime;
}
//若是找到顶端rootFiber,结束循环
if (node.return === null && node.tag === HostRoot) {
root = node.stateNode;
break;
}
node = node.return;
}
}
//更新该rootFiber的最旧、最新的挂起时间
if (root !== null) {
// Update the first and last pending expiration times in this root
const firstPendingTime = root.firstPendingTime;
if (expirationTime > firstPendingTime) {
root.firstPendingTime = expirationTime;
}
const lastPendingTime = root.lastPendingTime;
if (lastPendingTime === NoWork || expirationTime < lastPendingTime) {
root.lastPendingTime = expirationTime;
}
}
return root;
}
复制代码
解析:markUpdateTimeFromFiberToRoot()
主要作了如下事情
(1)更新fiber
对象的expirationTime
(2)根据fiber.return
向上遍历寻找RootFiber
(fiber的顶层对象)
(3)在向上遍历的过程当中,更新父对象fiber.return
子节点的childExpirationTime
关于RootFiber
,请参考:React源码解析之RootFiber
(4)找到RootFiber
后,根据RootFiber.stateNode=FiberRoot
的关系,找到FiberRoot
(5)更新该rootFiber
的最旧、最新的挂起时间
(6)返回RootFiber
4、checkForInterruption()
做用:
判断是否有高优先级任务打断当前正在执行的任务
源码:
//判断是否有高优先级任务打断当前正在执行的任务
function checkForInterruption(
fiberThatReceivedUpdate: Fiber,
updateExpirationTime: ExpirationTime,
) {
//若是任务正在执行,而且异步任务已经执行到一半了,
//可是如今须要把执行权交给浏览器,去执行优先级更高的任务
if (
enableUserTimingAPI &&
workInProgressRoot !== null &&
updateExpirationTime > renderExpirationTime
) {
//打断当前任务,优先执行新的update
interruptedBy = fiberThatReceivedUpdate;
}
}
复制代码
解析:
若是当前fiber
的优先级更高,须要打断当前执行的任务,当即执行该fiber
上的update
,则更新interruptedBy
5、getCurrentPriorityLevel()
做用:
获取当前调度任务的优先级
源码:
// Except for NoPriority, these correspond to Scheduler priorities. We use
// ascending numbers so we can compare them like numbers. They start at 90 to
// avoid clashing with Scheduler's priorities.
//除了90,用数字是由于这样作,方便比较
//从90开始的缘由是防止和Scheduler的优先级冲突
export const ImmediatePriority: ReactPriorityLevel = 99;
export const UserBlockingPriority: ReactPriorityLevel = 98;
export const NormalPriority: ReactPriorityLevel = 97;
export const LowPriority: ReactPriorityLevel = 96;
export const IdlePriority: ReactPriorityLevel = 95;
// NoPriority is the absence of priority. Also React-only.
export const NoPriority: ReactPriorityLevel = 90;
//获取当前调度任务的优先级
export function getCurrentPriorityLevel(): ReactPriorityLevel {
switch (Scheduler_getCurrentPriorityLevel()) {
//99
case Scheduler_ImmediatePriority:
return ImmediatePriority;
//98
case Scheduler_UserBlockingPriority:
return UserBlockingPriority;
//97
case Scheduler_NormalPriority:
return NormalPriority;
//96
case Scheduler_LowPriority:
return LowPriority;
//95
case Scheduler_IdlePriority:
return IdlePriority;
default:
invariant(false, 'Unknown priority level.');
}
}
复制代码
Scheduler_getCurrentPriorityLevel():
const {
unstable_getCurrentPriorityLevel: Scheduler_getCurrentPriorityLevel,
} = Scheduler;
function unstable_getCurrentPriorityLevel() {
return currentPriorityLevel;
}
//当前调度优先级默认为 NormalPriority
var currentPriorityLevel = NormalPriority;
复制代码
解析:
记住scheduler
优先级是90往上,而且默认是NormalPriority
(97)
若是是同步任务,而且是初次render()的话,会先执行schedulePendingInteractions()
6、schedulePendingInteractions()
做用:
跟踪须要同步执行的update
们,并计数、检测它们是否会报错
源码:
schedulePendingInteractions():
//跟踪这些update,并计数、检测它们是否会报错
function schedulePendingInteractions(root, expirationTime) {
// This is called when work is scheduled on a root.
// It associates the current interactions with the newly-scheduled expiration.
// They will be restored when that expiration is later committed.
//当调度开始时就执行,每调度一个update,就更新跟踪栈
if (!enableSchedulerTracing) {
return;
}
//调度的"交互"
scheduleInteractions(root, expirationTime, __interactionsRef.current);
}
复制代码
__interactionsRef:
// Set of currently traced interactions.
// Interactions "stack"–
// Meaning that newly traced interactions are appended to the previously active set.
// When an interaction goes out of scope, the previous set (if any) is restored.
//设置当前跟踪的interactions,也是interactions的栈
//它是一个集合
let interactionsRef: InteractionsRef = (null: any);
复制代码
scheduleInteractions():
//与schedule的交互
function scheduleInteractions(root, expirationTime, interactions) {
if (!enableSchedulerTracing) {
return;
}
//当interactions存在时
if (interactions.size > 0) {
//获取FiberRoot的pendingInteractionMap属性
const pendingInteractionMap = root.pendingInteractionMap;
//获取pendingInteractions的expirationTime
const pendingInteractions = pendingInteractionMap.get(expirationTime);
//若是pendingInteractions不为空的话
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);
});
}
//不然初始化pendingInteractionMap
//并统计当前调度中同步任务的数量
else {
pendingInteractionMap.set(expirationTime, new Set(interactions));
// Update the pending async work count for the current interactions.
interactions.forEach(interaction => {
interaction.__count++;
});
}
//计算并得出线程的id
const subscriber = __subscriberRef.current;
if (subscriber !== null) {
//这个暂时不看了
const threadID = computeThreadID(root, expirationTime);
//检测这些任务是否会报错
subscriber.onWorkScheduled(interactions, threadID);
}
}
}
复制代码
subscriber.onWorkScheduled():
//利用线程去检测同步的update,判断它们是否会报错
function onWorkScheduled(
interactions: Set<Interaction>,
threadID: number,
): void {
let didCatchError = false;
let caughtError = null;
//遍历去检测
subscribers.forEach(subscriber => {
try {
subscriber.onWorkScheduled(interactions, threadID);
} catch (error) {
if (!didCatchError) {
didCatchError = true;
caughtError = error;
}
}
});
if (didCatchError) {
throw caughtError;
}
}
复制代码
解析:
利用FiberRoot
的pendingInteractionMap
属性和不一样的expirationTime
,获取每次schedule
所需的update
任务的集合,记录它们的数量,并检测这些任务是否会出错。
7、renderRoot()
做用:
初始化root,并调用workLoop进行循环单元更新
这个咱们放在后面再讲。
后言:
原本是写到十一了,但发现还有一层套一层的function
,所有放在一篇文章里的话,太长了,容易消化不了,因此咱们先在这里打住:
export function scheduleUpdateOnFiber(){
xxx
xxx
xxx
if (expirationTime === Sync) {
if( 第一次render ){
//跟踪这些update,并计数、检测它们是否会报错
schedulePendingInteractions(root, expirationTime);
//初始化root,调用workLoop进行循环单元更新
let callback = renderRoot(root, Sync, true);
}else{
下篇要讲的内容。。。。
}
}
}
复制代码
scheduleWork 结束后,我会像往常同样,制做一张流程图帮助你们梳理思路!
源码请参考GitHub:
github.com/AttackXiaoJ…
(未完待续!)