React Fiber源码分析 第三篇(异步状态)

react

先附上流程图~

调用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源码分析 第三篇(异步状态) 的全部内容, 来源链接: utcz.com/z/382664.html

回到顶部