前言: 你需要知道:浅谈React 16中的Fiber机制(https://tech.youzan.com/react-fiber/)、React源码解析之RootFiber、React源码解析之FiberRoot
在之前的文章中讲到,React更新的方式有三种: (1)ReactDOM.render() || hydrate(ReactDOMServer渲染) (2)setState() (3)forceUpdate()
在createUpdate
后就进入scheduleWork
流程,接下来我们就正式进入调度流程
一、scheduleUpdateOnFiber()
作用:
调度update
任务
提示:
scheduleWork
即scheduleUpdateOnFiber
:
export const scheduleWork = scheduleUpdateOnFiber;
源码:
//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);
}
}
}
}
解析:
有点长,我们慢慢看↓
二、checkForNestedUpdates()
作用:
判断是否是无限循环的update
源码:
// 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()
setState死循环
三、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
四、checkForInterruption() 作用: 判断是否有高优先级任务打断当前正在执行的任务
源码:
//判断是否有高优先级任务打断当前正在执行的任务
function checkForInterruption(
fiberThatReceivedUpdate: Fiber,
updateExpirationTime: ExpirationTime,
) {
//如果任务正在执行,并且异步任务已经执行到一半了,
//但是现在需要把执行权交给浏览器,去执行优先级更高的任务
if (
enableUserTimingAPI &&
workInProgressRoot !== null &&
updateExpirationTime > renderExpirationTime
) {
//打断当前任务,优先执行新的update
interruptedBy = fiberThatReceivedUpdate;
}
}
解析:
如果当前fiber
的优先级更高,需要打断当前执行的任务,立即执行该fiber
上的update
,则更新interruptedBy
五、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()
六、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
任务的集合,记录它们的数量,并检测这些任务是否会出错。
七、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: https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberWorkLoop.js