先附上流程图~

调用setState时, 会调用classComponentUpdater的enqueueSetState方法, 同时将新的state作为payload参数传进

enqueueSetState会先调用requestCurrentTime获取一个currentTime,

function requestCurrentTime() {
// 维护两个时间 一个renderingTime 一个currentSechedulerTime
// rederingTime 可以随时更新 currentSechedulerTime只有在没有新任务的时候才更新
if (isRendering) {
return currentSchedulerTime;
}
findHighestPriorityRoot();
if (nextFlushedExpirationTime === NoWork || nextFlushedExpirationTime === Never) {
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
return currentSchedulerTime;
}
return currentSheculerTime

通过获取到的currentTime, 调用computeExpirationForFiber,计算该fiber的优先级,

if (fiber.mode & AsyncMode) {
if (isBatchingInteractiveUpdates) {
// This is an interactive update
expirationTime = computeInteractiveExpiration(currentTime);
} else {
// This is an async update
expirationTime = computeAsyncExpiration(currentTime);
}
...
}

这个函数其他点比较简单, 里面主要有下面 这个判断要说明一下, 如果是属于异步更新的话,会根据是 交互引起的更新 还是其他更新 来调用不同的函数computeInteractiveExpiration和computeAsyncExpiration,

可以看到这两个函数最后返回的都是computeExpirationBucket函数的结果, 只是入参不同, computeInteractiveExpiration的参数是500, 100,  computeAsyncExpiration的参数是5000, 250, 然后看computeExpirationBucket函数可以看到, 第二个参数(500和5000)越大,则返回的expirationTime越大, 也就是说 computeInteractiveExpiration的更新优先级高于computeAsyncExpiration, 则交互的优先级高于其他

获得优先级后则和同步更新一样, 创建update并放进队列, 然后调用sheuduleWork

var classComponentUpdater = {
isMounted: isMounted,
enqueueSetState: function (inst, payload, callback) {
var fiber = get(inst);
   // 获得优先级
var currentTime = requestCurrentTime();
var expirationTime = computeExpirationForFiber(currentTime, fiber);
   // 创建更新
var update = createUpdate(expirationTime);
update.payload = payload;
if (callback !== undefined && callback !== null) {
update.callback = callback;
} enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
},

接下来的步骤和同步一样, 直到同步调用的是performSyncWork函数, 而异步调用的是scheduleCallbackWithExpirationTime函数

scheduleCallbackWithExpirationTime函数首先判断是否存在callback正在进行中,  判断现有expirationTime和其优先级,若优先级比较低则直接返回, 否则设置现在的fiber任务为新的callback,并把原来的回调从列表中移除

function scheduleCallbackWithExpirationTime(root, expirationTime) {
if (callbackExpirationTime !== NoWork) {
// 判断优先级
if (expirationTime > callbackExpirationTime) {
// Existing callback has sufficient timeout. Exit.
return;
} else {
if (callbackID !== null) {
// 取消, 从回调列表中删除
schedule.unstable_cancelScheduledWork(callbackID);
}
}
// The request callback timer is already running. Don't start a new one.
}
// 设置新的callback和callbackExiporationTime
callbackExpirationTime = expirationTime;
var currentMs = schedule.unstable_now() - originalStartTimeMs;
var expirationTimeMs = expirationTimeToMs(expirationTime);
// 计算是否超时
var timeout = expirationTimeMs - currentMs;
callbackID = schedule.unstable_scheduleWork(performAsyncWork, { timeout: timeout });
}

接下来调用schedule.unstable_scheduleWork(performAsyncWork, { timeout: timeout })函数, 并生成一个节点, 存储回调函数和超时时间,插入到回调列表, 并根据超时排序, 调用ensureHostCallBackIsScheduled函数,最后返回该节点

function unstable_scheduleWork(callback, options) {
var currentTime = exports.unstable_now(); var timesOutAt;
// 获取超时时间
if (options !== undefined && options !== null && options.timeout !== null && options.timeout !== undefined) {
// Check for an explicit timeout
timesOutAt = currentTime + options.timeout;
} else {
// Compute an absolute timeout using the default constant.
timesOutAt = currentTime + DEFERRED_TIMEOUT;
}
 // 生成一个节点, 存储回调函数和超时时间
var newNode = {
callback: callback,
timesOutAt: timesOutAt,
next: null,
previous: null
}; // 插入到回调列表, 并根据超时排序, 最后返回该节点
if (firstCallbackNode === null) {
// This is the first callback in the list.
firstCallbackNode = newNode.next = newNode.previous = newNode;
ensureHostCallbackIsScheduled(firstCallbackNode);
} else {
...var previous = next.previous;
previous.next = next.previous = newNode;
newNode.next = next;
newNode.previous = previous;
} return newNode;
}

ensureHostCallBackIsScheduled函数如名, 相对比较简单

function ensureHostCallbackIsScheduled() {
if (isPerformingWork) {
// Don't schedule work yet; wait until the next time we yield.
return;
}
// Schedule the host callback using the earliest timeout in the list.
var timesOutAt = firstCallbackNode.timesOutAt;
if (!isHostCallbackScheduled) {
isHostCallbackScheduled = true;
} else {
// Cancel the existing host callback.
cancelCallback();
}
requestCallback(flushWork, timesOutAt);
}

往下看requestCallback, 这里说的如果已经在执行任务的话, 就必须有一个错误被抛出(抛出的错误是啥??),同时不要等待下一帧, 尽快开始新事件

如果如果当前没有调度帧回调函数,我们需要进行一个调度帧回调函数, 并设置isAnimationFrameScheduled为true,
接着执行requestAnimationFrameWithTimeout;函数


requestCallback = function (callback, absoluteTimeout) {
scheduledCallback = callback;
timeoutTime = absoluteTimeout;
if (isPerformingIdleWork) {
// 如果已经在执行任务的话, 就必须有一个错误被抛出(抛出的错误是啥??),同时不要等待下一帧, 尽快开始新事件
window.postMessage(messageKey, '*');
} else if (!isAnimationFrameScheduled) {
isAnimationFrameScheduled = true;
requestAnimationFrameWithTimeout(animationTick);
}
};

requestAnimationFrameWithTimeout函数就是执行一个异步操作, 执行完毕后, 假设此时又有N个回调任务进入, 同时原来的回调还没有进行, 则回到scheduleCallbackWithExpirationTime函数上,

分为两个分支: 1. 假设优先级低于目前的回调任务, 则直接返回(已经把root加到root队列中)
                          2. 优先级高于目前的回调任务, 将目前的回调任务从列表中移除, 并将callBackID设为传入的回调, 接下来的路线与上面一致, 假设该传入的回调超时最早, 则会进入到cancelCallback函数,重                                  置各变量, 并进入到requestCallback函数, 此时除了赋值操作, 没有其他动作

到了这时候, 已经把新的回调替换正在进行的回调到回调列表。
函数正常执行, 调用callback, 即animationTick函数

cancelCallback = function () {
scheduledCallback = null;
isIdleScheduled = false;
timeoutTime = -1;
};
var ANIMATION_FRAME_TIMEOUT = 100;
var rAFID;
var rAFTimeoutID;
var requestAnimationFrameWithTimeout = function (callback) {
// schedule rAF and also a setTimeout
rAFID = localRequestAnimationFrame(function (timestamp) {
// cancel the setTimeout
localClearTimeout(rAFTimeoutID);
callback(timestamp);
});
rAFTimeoutID = localSetTimeout(function () {
// cancel the requestAnimationFrame
localCancelAnimationFrame(rAFID);
callback(exports.unstable_now());
}, ANIMATION_FRAME_TIMEOUT);
};

animationTick一个是把isAnimationFrameScheduled状态设为false, 即不在调度帧回调的状态, 同时计算帧到期时间frameDeadline , 判断是否在帧回调的状态, 否的话调用window.postMessage ,并设置isIdleScheduled状态为true

假设此时, 有N个回调进入, 分为两个情况: 1.假设优先级低于目前的回调任务, 则直接返回(已经把root加到root队列中)
                                                                          2.优先级高于目前的回调任务, 将目前的回调任务从列表中移除, 并将callBackID设为传入的回调, 接下来的路线与上面一致,一直到animationTick函数,因为                                                                                         postMessage比setTImeout更快执行,所以此时isIdleScheduled为false,和之前一样正常执行。

var animationTick = function (rafTime) {
isAnimationFrameScheduled = false;
...
...
// 每帧到期时间为33ms
frameDeadline = rafTime + activeFrameTime;
if (!isIdleScheduled) {
isIdleScheduled = true;
window.postMessage(messageKey, '*');
}
};

postMessage会执行idleTick , 首先把isIdleScheduled\didTimeout置为false,

先判断帧到期时间和超时时间是否小于当前时间, 如果是的话, 则置didTimeout为true,
如果帧到期, 但超时时间小于当前时间, 则置isAnimationFrameScheduled 为false, 并调用requestAnimationFrameWithTimeout, 即进入下一帧
如果帧未到期, 则调用callbak函数, 并把isPerformingIdleWork置为true

idleTick 会先执行callback, 完成后才将isPerformingIdleWork 置为false, 执行callback的时候会传入didTimeout作为参数, callback为flushWork

 var idleTick = function (event) {
...
isIdleScheduled = false; var currentTime = exports.unstable_now(); var didTimeout = false;
if (frameDeadline - currentTime <= 0) {
// 帧过期
if (timeoutTime !== -1 && timeoutTime <= currentTime) {
// 回调超时
didTimeout = true;
} else {
// No timeout.
if (!isAnimationFrameScheduled) {
// 到下一帧继续任务
isAnimationFrameScheduled = true;
requestAnimationFrameWithTimeout(animationTick);
}
// Exit without invoking the callback.
return;
}
} timeoutTime = -1;
var callback = scheduledCallback;
scheduledCallback = null;
if (callback !== null) {
isPerformingIdleWork = true;
try {
callback(didTimeout);
} finally {
isPerformingIdleWork = false;
}
}
};

flushwork首先把isPerformingWork置为true, 然后把didTimeout赋值给deallinObject对象, 接下来进行判断
如果已经过了帧的结束期, 则判断链表中有哪个节点已超时, 并循环调用flushFirstCallback函数解决超时节点,
如果还没有过帧的结束期, 则调用flushFirstCallback函数处理链表中的第一个节点, 循环处理一直到该帧结束

最后, flushwork函数会将isPerformingWork置为false, 并判断是否还有任务 有则执行ensureHostCallbackIsScheduled函数

function flushWork(didTimeout) {
isPerformingWork = true;
deadlineObject.didTimeout = didTimeout;
try {
if (didTimeout) {
while (firstCallbackNode !== null) {
var currentTime = exports.unstable_now();
if (firstCallbackNode.timesOutAt <= currentTime) {
do {
flushFirstCallback();
} while (firstCallbackNode !== null && firstCallbackNode.timesOutAt <= currentTime);
continue;
}
break;
}
} else {
// Keep flushing callbacks until we run out of time in the frame.
if (firstCallbackNode !== null) {
do {
flushFirstCallback();
} while (firstCallbackNode !== null && getFrameDeadline() - exports.unstable_now() > 0);
}
}
} finally {
isPerformingWork = false;
if (firstCallbackNode !== null) {
// There's still work remaining. Request another callback.
ensureHostCallbackIsScheduled(firstCallbackNode);
} else {
isHostCallbackScheduled = false;
}
}
}

继续往下看, 则是flushFirstCallback函数,先把该节点从链表中清掉, 然后调用callback函数, 并带入deadlineObject作为参数

function flushFirstCallback(node) {
var flushedNode = firstCallbackNode; //从链表中清理掉该节点, 这样哪怕出错了, 也能保留原链表状态
var next = firstCallbackNode.next;
if (firstCallbackNode === next) {
// This is the last callback in the list.
firstCallbackNode = null;
next = null;
} else {
var previous = firstCallbackNode.previous;
firstCallbackNode = previous.next = next;
next.previous = previous;
} flushedNode.next = flushedNode.previous = null; // Now it's safe to call the callback.
var callback = flushedNode.callback;
callback(deadlineObject);
}

接下来的就是performAsyncWork函数,如果didTimeout为true, 则表明至少有一个更新已过期, 迭代所有root任务, 把已过期的root的nextExpirationTimeToWorkOn重置为当前时间currentTime.
然后调用performWork(Nowork, dl)函数

function performAsyncWork(dl) {
if (dl.didTimeout) {
// 刷新所有root的nextEpirationTimeToWorkOn
if (firstScheduledRoot !== null) {
recomputeCurrentRendererTime();
var root = firstScheduledRoot;
do {
didExpireAtExpirationTime(root, currentRendererTime);
// The root schedule is circular, so this is never null.
root = root.nextScheduledRoot;
} while (root !== firstScheduledRoot);
}
}
performWork(NoWork, dl);
}

performWork函数在之前已经分析过了, 这里主要看存在deadline时的操作, 在帧未到期 或者 当前渲染时间大于等于nextFlushedExpirationTime时才执行 performWorkOnRoot, 并将currentRendererTime >= nextFlushedExpirationTime作为第三个参数传入, 一直循环处理任务,
最后清除callbackExpirationTime, callBackId, 同时, 如果还有任务的话, 则继续调用scheduleCallbackWithExpirationTime(nextFlushedRoot, nextFlushedExpirationTime);函数进入到回调

function performWork(minExpirationTime, dl) {
deadline = dl; // Keep working on roots until there's no more work, or until we reach
// the deadline.
findHighestPriorityRoot(); if (deadline !== null) {
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || minExpirationTime >= nextFlushedExpirationTime) && (!deadlineDidExpire || currentRendererTime >= nextFlushedExpirationTime)) {
performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, currentRendererTime >= nextFlushedExpirationTime);
findHighestPriorityRoot();
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
}
}
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; finishRendering();
}

接下来看异步状态下的performWorkOnRoot函数。基本操作和同步一样, 在进入到renderRoot(root, _isYieldy, isExpired);函数时, 会根据是否已超时将isYieldy置为true或者false, 异步状态下未超时为false,
renderRoot和同步一样, 最后执行workLoop(isYieldy)
workLoop在未过期的情况下, 会执行shouldYield()函数来判断是否执行nextUnitOfWork, 和同步一样, 这里只需要关注shouldYied函数

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);
}
}
}

shouldYield函数, 如果deadlineDidExpire为true, 即帧已到期, 直接返回true,
如果deadline不存在, 并且帧未到期, 则返回false, 可以执行单元
否则将deadlineDidExpire置为true

function shouldYield() {
if (deadlineDidExpire) {
return true;
}
if (deadline === null || deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
// Disregard deadline.didTimeout. Only expired work should be flushed
// during a timeout. This path is only hit for non-expired work.
return false;
}
deadlineDidExpire = true;
return true;
}
完结~撒花

React Fiber源码分析 第三篇(异步状态)的更多相关文章

  1. React Fiber源码分析 (介绍)

    写了分析源码的文章后, 总觉得缺少了什么, 在这里补一个整体的总结,输出个人的理解~ 文章的系列标题为Fiber源码分析, 那么什么是Fiber,官方给出的解释是: React Fiber是对核心算法 ...

  2. React Fiber源码分析 第二篇(同步模式)

    先附上两张流程图 1.scheduleRootUpdate  这个函数主要执行了两个操作  1个是创建更新createUpdate并放到更新队列enqueueUpdate, 1个是执行sheculeW ...

  3. React Fiber源码分析 第一篇

    先附上流程图一张 先由babel编译, 调用reactDOM.render,入参为element, container, callback, 打印出来可以看到element,container,cal ...

  4. JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor

    JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor.它主要用来在 ...

  5. JUC源码分析-线程池篇(三)Timer

    JUC源码分析-线程池篇(三)Timer Timer 是 java.util 包提供的一个定时任务调度器,在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次. 1. Ti ...

  6. JUC源码分析-线程池篇(一):ThreadPoolExecutor

    JUC源码分析-线程池篇(一):ThreadPoolExecutor Java 中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池.在开发过程中,合理地使用线程池 ...

  7. Spring MVC源码分析(三):SpringMVC的HandlerMapping和HandlerAdapter的体系结构设计与实现

    概述在我的上一篇文章:Spring源码分析(三):DispatcherServlet的设计与实现中提到,DispatcherServlet在接收到客户端请求时,会遍历DispatcherServlet ...

  8. 鸿蒙内核源码分析(Shell编辑篇) | 两个任务,三个阶段 | 百篇博客分析OpenHarmony源码 | v71.01

    子曰:"我非生而知之者,好古,敏以求之者也." <论语>:述而篇 百篇博客系列篇.本篇为: v71.xx 鸿蒙内核源码分析(Shell编辑篇) | 两个任务,三个阶段 ...

  9. 鸿蒙内核源码分析(字符设备篇) | 字节为单位读写的设备 | 百篇博客分析OpenHarmony源码 | v67.01

    百篇博客系列篇.本篇为: v67.xx 鸿蒙内核源码分析(字符设备篇) | 字节为单位读写的设备 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一 ...

随机推荐

  1. C++ Thrift服务端记录调用者IP和被调接口方法

    Apache开源的Thrift(http://thrift.apache.org)有着广泛的使用,有时候需要知道谁调用了指定的函数,比如在下线一起老的接口之前,需要确保对这些老接口的访问已全部迁移到新 ...

  2. Core Expression

    https://docs.oracle.com/cd/E12058_01/doc/doc.1014/e12030/cron_expressions.htm A Cron Expressions Cro ...

  3. 文本超过控件长度自动显示省略号的css

    overflow: hidden; white-space: nowrap; text-overflow: ellipsis;

  4. Hive数据倾斜解决方案

    https://blog.csdn.net/yu0_zhang0/article/details/81776459 https://blog.csdn.net/lxpbs8851/article/de ...

  5. poj 1050 最大子矩阵

    a11   a12   a13   a14   a15 a21   a22   a23   a24   a25 a31   a32   a33   a34   a35 a41   a42   a43  ...

  6. 逆向暴力求解 538.D Weird Chess

    11.12.2018 逆向暴力求解 538.D Weird Chess New Point: 没有读好题 越界的情况无法判断,所以输出任何一种就可以 所以他给你的样例输出完全是误导 输出还搞错了~ 输 ...

  7. unigui+fastReport实现web打印方案

    近日单位需要用到会议通知单的打印功能,故引出篇. unigui是delphi环境下快速开发web应用的优秀工具,不再赘述,下面直接记录使用搭配使用,基本逻辑就是: unigui实现数据录入和浏览的we ...

  8. java基础点总结

    基础知识这种东西,没注意到的永远比想象中多.大部分都是在面试中问到的... 1.static关键字 变量,方法修饰;静态代码块;静态内部类; 静态导入:import static ,静态方法省略类名, ...

  9. 开源网盘云存储 Seafile

    摘要: Seafile 是一款安全.高性能的开源网盘(云存储)软件.Seafile 提供了主流网盘(云盘)产品所具有的功能,包括文件同步.文件共享等.在此基础上,Seafile 还提供了高级的安全保护 ...

  10. Kali学习笔记33:Linux系统缓冲区溢出实验

    之前做过一个Windows应用SLmail的缓冲区溢出的实验 这次来做一个Linux平台的缓冲区溢出实验: 缓冲区溢出是什么? 学过汇编的应该知道,当缓冲区边界限制不严格时,由于变量传入畸形数据或程序 ...