博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React Fiber源码分析 第三篇(异步状态)
阅读量:6329 次
发布时间:2019-06-22

本文共 13264 字,大约阅读时间需要 44 分钟。

系列文章

前言

React Fiber是React在V16版本中的大更新,利用了闲余时间看了一些源码,做个小记录~

流程图

image

源码分析

1.调用setState时, 会调用classComponentUpdaterenqueueSetState方法, 同时将新的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

2.通过获取到的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);      }      ...    }

3.这个函数其他点比较简单, 里面主要有下面 这个判断要说明一下, 如果是属于异步更新的话,会根据是 交互引起的更新 还是其他更新 来调用不同的函数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);  },

4.接下来的步骤和同步一样, 直到同步调用的是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 });}

5.接下来调用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;}

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

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

如果如果当前没有调度帧回调函数,我们需要进行一个调度帧回调函数, 并设置isAnimationFrameScheduledtrue,

接着执行requestAnimationFrameWithTimeout;函数

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

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

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

假设此时, 有N个回调进入, 分为两个情况:

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

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

10.postMessage会执行idleTick , 首先把isIdleScheduleddidTimeout置为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;      }    }  };

11.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;    }  }}

12.继续往下看, 则是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);}

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

然后调用performWork函数

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

14.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();}

15.接下来看异步状态下的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);    }  }}

16.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;}

总结

源码分析到这里就结束啦,下一篇做一个总结,不然就是流水账一样的,容易忘记

转载地址:http://ccwoa.baihongyu.com/

你可能感兴趣的文章
从国内向海外转移域名经验谈
查看>>
浅谈apache与tomact的整合
查看>>
OAuth与SSO、REST有哪些区别与联系
查看>>
详解XStream别名
查看>>
Exchange 2016集成ADRMS系列-1:先决条件准备
查看>>
SQL Server vNext CTP1 on Linux
查看>>
1-为 Lync Server 2010 准备 Active Directory 域服务
查看>>
统一沟通-技巧-10-Lync-公网证书-Go Daddy
查看>>
Grid Control 10G安装与配置
查看>>
云服务商到了认真考虑“自动化”的时候了
查看>>
测试应该在产品开发的哪个阶段进入?
查看>>
SELinux安全
查看>>
大话IT第21期:红帽资助四大开源基金会
查看>>
浅谈微服务的来龙去脉
查看>>
项目经理,别让猴子跳回背上!
查看>>
NetBackup下ORACLE恢复测试方案实例解析
查看>>
【有奖征文】“失业”程序员的苦辣酸甜
查看>>
nagios监控web/mysql多角度实战分享(一)
查看>>
SCOM 2012系列⑦即时消息通知上
查看>>
IE9是如何被FireFox4超越全球市场份额的?
查看>>