共计 8850 个字符,预计需要花费 23 分钟才能阅读完成。
系列文章
React Fiber 源码分析 第一篇 React Fiber 源码分析 第二篇(同步模式)
前言
React Fiber 是 React 在 V16 版本中的大更新,利用了闲余时间看了一些源码,做个小记录~ 如果有错误, 请轻喷
流程图
流程图 1
流程图 2
源码分析
1.scheduleRootUpdate 这个函数主要执行了两个操作 1 个是创建更新 createUpdate 并放到更新队列 enqueueUpdate,1 个是执行 sheculeWork 函数
function scheduleRootUpdate(current$$1, element, expirationTime, callback) {
var update = createUpdate(expirationTime);
update.payload = {element: element};
callback = callback === undefined ? null : callback;
if (callback !== null) {
update.callback = callback;
}
enqueueUpdate(current$$1, update);
scheduleWork(current$$1, expirationTime);
return expirationTime;
}
2. 先从 createUpdate 函数分析,他直接返回了一个包含了更新信息的对象
function createUpdate(expirationTime) {
return {
// 优先级
expirationTime: expirationTime,
// 更新类型
tag: UpdateState,
// 更新的对象
payload: null,
callback: null,
// 指向下一个更新
next: null,
// 指向下一个更新 effect
nextEffect: null
};
}
3. 接着更新 payload 和 callback 属性,payload 即为更新的对象,然后执行 enqueuUpdate,enqueueUpdate 相对比较容易理解,不过里面有一注释挺重要
Both queues are non-empty. The last update is the same in both lists, because of structural sharing. So, only append to one of the lists 意思是 alternate 的 updateQueue 和 fiber 的 updateQueue 是同一个对象引用,这里会在 createWorkInProcess 提到
往下走就是重要的 scheduleWork, 它是 render 阶段真正的开始
function scheduleWork(fiber, expirationTime) {
// 更新优先级
var root = scheduleWorkToRoot(fiber, expirationTime);
…if (!isWorking && nextRenderExpirationTime !== NoWork && expirationTime < nextRenderExpirationTime) {
// This is an interruption. (Used for performance tracking.) 如果这是一个打断原有更新的任务,先把现有任务记录
interruptedBy = fiber;
resetStack();
}
// 设置下一个操作时间 nextExpirationTimeToWorkOn
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$1 ||
// …unless this is a different root than the one we’re rendering.
nextRoot !== root) {
var rootExpirationTime = root.expirationTime;
requestWork(root, rootExpirationTime);
}
…
}
4.scheduleWork 先执行一个 scheduleWorkToRoot 函数,该函数主要是更新其 expirationTime 以及上层 fiber 的 childrenExpirationTime
function scheduleWorkToRoot(fiber, expirationTime) {
// Update the source fiber’s expiration time
if (fiber.expirationTime === NoWork || fiber.expirationTime > expirationTime) {
fiber.expirationTime = expirationTime;
}
var alternate = fiber.alternate;
if (alternate !== null && (alternate.expirationTime === NoWork || alternate.expirationTime > expirationTime)) {
alternate.expirationTime = expirationTime;
}
// 如果是 HostRoot 即直接返回
var node = fiber.return;
if (node === null && fiber.tag === HostRoot) {
return fiber.stateNode;
}
// 若子 fiber 中有更新,即更新其 childrenExpirationTime
while (node !== null) {
…
}
return null;
}
5. 接着会执行一个 markPendingPriorityLevel 函数,这个函数主要是更新 root 的最高优先级和最低优先级(earliestPendingTime 和 lastestPendingTime;), 同时设置下一个执行操作的时间 nextExpirationTimeToWorkOn(即 root 中具有最高优先级的 fiber 的 expirationTime),关于这个函数的 latestSuspendedTime; 以后再说
最后 scheduleWork 会执行 requestWork
function requestWork(root, expirationTime) {
addRootToSchedule(root, expirationTime);
if (isRendering) {
// rendering 状态,直接返回
return;
}
if (isBatchingUpdates) {
// isBatchingUpdates,直接返回。react 的 state 更新是会合并的
…return;
}
// TODO: Get rid of Sync and use current time?
if (expirationTime === Sync) {
// 执行同步
performSyncWork();
} else {
// 异步,暂不分析
scheduleCallbackWithExpirationTime(root, expirationTime);
}
}
6.requestWork 会先执行 addRootToSchedule,由函数名称可知其作用,将 root 加到 schedule, 即设置 firstScheduledRoot,lastScheduledRoot 以及他们的 nextScheduleRoot 属性,说白了就是一个闭环链式结构 first => next => next => last(next => first), 同时更新 root 的 expirationTime 属性
function addRootToSchedule(root, expirationTime) {
// root 尚未开始过任务 将 root 加到 schedule
if (root.nextScheduledRoot === null) {
…
} else {
// root 已经开始执行过任务,更新 root 的 expirationTime
var remainingExpirationTime = root.expirationTime;
if (remainingExpirationTime === NoWork || expirationTime < remainingExpirationTime) {
root.expirationTime = expirationTime;
}
}
}
7. 接着 requestWork 会判断是否正在渲染中,防止重入。剩余的工作将安排在当前渲染批次的末尾,如果正在渲染直接返回后,因为已经把 root 加上到 Schedule 里面了,依然会把该 root 执行同时判断是否正在 batch update,这里留到分析 setState 的时候说,最后根据异步或者同步执行不同函数,此处执行同步 performSyncWork(),performSyncWork 直接执行 performWork(Sync, null);
function performWork(minExpirationTime, dl) {
deadline = dl;
// 找出优先级最高的 root
findHighestPriorityRoot();
if (deadline !== null) {
// … 异步
} else {
// 循环执行 root 任务
while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || minExpirationTime >= nextFlushedExpirationTime)) {
performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, true);
findHighestPriorityRoot();
}
}
…
// If there’s work left over, schedule a new callback.
if (nextFlushedExpirationTime !== NoWork) {
scheduleCallbackWithExpirationTime(nextFlushedRoot, nextFlushedExpirationTime);
}
…
}
8.performWork 首先执行 findHighestPriorityRoot 函数。findHighestPriorityRoot 函数主要执行两个操作,一个是判断当前 root 是否还有任务,如果没有,则从 firstScheuleRoot 链中移除。一个是找出优先级最高的 root 和其对应的优先级并赋值给 nextFlushedRootnextFlushedExpirationTime
function findHighestPriorityRoot() {
var highestPriorityWork = NoWork;
var highestPriorityRoot = null;
if (lastScheduledRoot !== null) {
var previousScheduledRoot = lastScheduledRoot;
var root = firstScheduledRoot;
while (root !== null) {
var remainingExpirationTime = root.expirationTime;
if (remainingExpirationTime === NoWork) {
// 判断是否还有任务并移除
} else {
// 找出最高的优先级 root 和其对应的优先级
}
}
}
// 赋值
nextFlushedRoot = highestPriorityRoot;
nextFlushedExpirationTime = highestPriorityWork;
}
9. 紧着,performWork 会根据传入的参数 dl 来判断进行同步或者异步操作,这里暂不讨论异步,
while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || minExpirationTime >= nextFlushedExpirationTime)) {
performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, true);
findHighestPriorityRoot();
}
10. 接着,会进行 performWorkOnRoot 函数,并传入优先级最高的 root 和其对应的 expirationTime 以及一个 true 作为参数,performWorkOnRoot 函数的第三个参数 isExpired 主要是用来判断是否已超过执行时间,由于进行的是同步操作,所以默认超过 performWorkOnRoot 函数会先将 rendering 状态设为 true, 然后判断是否异步或者超时进行操作
function performWorkOnRoot(root, expirationTime, isExpired) {
// 将 rendering 状态设为 true
isRendering = true;
// Check if this is async work or sync/expired work.
if (deadline === null || isExpired) {
// Flush work without yielding.
// 同步
var finishedWork = root.finishedWork;
if (finishedWork !== null) {
// This root is already complete. We can commit it.
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = null;
// If this root previously suspended, clear its existing timeout, since
// we’re about to try rendering again.
var timeoutHandle = root.timeoutHandle;
if (enableSuspense && timeoutHandle !== noTimeout) {
root.timeoutHandle = noTimeout;
// $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
cancelTimeout(timeoutHandle);
}
var isYieldy = false;
renderRoot(root, isYieldy, isExpired);
finishedWork = root.finishedWork;
if (finishedWork !== null) {
// We’ve completed the root. Commit it.
completeRoot(root, finishedWork, expirationTime);
}
}
} else {
// Flush async work. 异步操作
……
}
}
isRendering = false;
}
11.renderRoot 的产物会挂载到 root 的 finishWork 属性上,首先 performWorkOnRoot 会先判断 root 的 finishWork 是否不为空,如果存在的话则直接进入 commit 的阶段,否则进入到 renderRoot 函数,设置 finishWork 属性 renderRoot 有三个参数,renderRoot(root, isYieldy, isExpired),同步状态下 isYield 的值是 false,renderRoot 先将 isWorking 设为 true,
renderRoot 会先判断是否是一个从新开始的 root,是的话会重置各个属性
首先是 resetStach() 函数,对原有的进行中的 root 任务中断,进行存储紧接着将 nextRootnextRendeExpirationTime 重置,同时创建第一个 nextUnitOfWork, 也就是一个工作单元这个 nextUnitOfWork 也是一个 workProgress, 也是 root.current 的 alternater 属性,而它的 alternate 属性则指向了 root.current,形成了一个双缓冲池
if (expirationTime !== nextRenderExpirationTime || root !== nextRoot || nextUnitOfWork === null) {
// 判断是否是一个从新开始的 root
resetStack();
nextRoot = root;
nextRenderExpirationTime = expirationTime;
nextUnitOfWork = createWorkInProgress(nextRoot.current, null, nextRenderExpirationTime);
root.pendingCommitExpirationTime = NoWork;
….
….
}
12. 接着执行 wookLoop(isYield)函数,该函数通过循环执行,遍历每一个 nextUniOfWork,
function workLoop(isYieldy) {
if (!isYieldy) {
// Flush work without yielding
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// Flush asynchronous work until the deadline runs out of time.
while (nextUnitOfWork !== null && !shouldYield()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}
13.performUnitOfWork 先 获取 参数的 alaernate 属性,赋值给 current, 根据注释的意思,workInProgress 是作为一个代替品存在来操作,然后会执行下面这个语句
next = beginWork(current$$1, workInProgress, nextRenderExpirationTime);
14.beginWork 主要根据 workInprogress 的 tag 来做不同的处理,并返回其 child,也就是下一个工作单元 如 <div><p></p><div>, div 作为一个工作单元,处理完后就返回工作单元 p,同时收集他们的 effect
若 next 存在,则返回到 workLoop 函数继续循环,若不存在,则执行 completeUnitOfWork(workInProgress) 函数
completeUnitOfWork 函数,会判断是否有 sibiling, 有则直接返回赋值给 next, 否则判断父 fiber 是否有 sibiling, 一直循环到最上层父 fiber 为 null,执行的同时会把 effect 逐级传给父 fiber
这个时候函数执行完毕,会返回到 renderRoot 函数,renderRoot 函数继续往下走
首先将 isWorking = false; 执行,然后会判断 nextUnitWork 是否为空,否的话则将 root.finishWork 设为空(异步,该任务未执行完)并结束函数
isWorking = false;
if (nextUnitOfWork !== null) {
onYield(root);
return;
}
重置 nextRoot 等
nextRoot = null;
interruptedBy = null;
赋值 finishWork
var rootWorkInProgress = root.current.alternate;
onComplete(root, rootWorkInProgress, expirationTime);
function onComplete(root, finishedWork, expirationTime) {
root.pendingCommitExpirationTime = expirationTime;
root.finishedWork = finishedWork;
}
15. 返回到 performWorkOnRoot 函数,进入 commit 阶段,将 rending 状态设为 false, 返回到 performWork 函数,继续进入循环执行 root,直到所有 root 完成
重置各个状态量,如果还存在 nextFlushedExpirationTime 不为空,则进行 scheduleCallbackWithExpirationTime 函数异步操作
if (deadline !== null) {
callbackExpirationTime = NoWork;
callbackID = null;
}
// If there’s work left over, schedule a new callback.
if (nextFlushedExpirationTime !== NoWork) {
scheduleCallbackWithExpirationTime(nextFlushedRoot, nextFlushedExpirationTime);
}
// Clean-up.
deadline = null;
deadlineDidExpire = false;
结语
以上就是同步模式下的源码分析~