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

先附上流程图~

调用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

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

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

可以看到这两个函数最后返回的都是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, 这里说的如果已经在执行任务的话, 就必须有一个错误被抛出(抛出的错误是啥??),同时不要等待下一帧, 尽快开始新事件

如果如果当前没有调度帧回调函数,我们需要进行一个调度帧回调函数, 并设置isAnimationFrameScheduledtrue,
接着执行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函数,因为                                                                                         postMessagesetTImeout更快执行,所以此时isIdleScheduledfalse,和之前一样正常执行。

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

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

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

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

 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函数,如果didTimeouttrue, 则表明至少有一个更新已过期, 迭代所有root任务, 把已过期的rootnextExpirationTimeToWorkOn重置为当前时间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函数, 如果deadlineDidExpiretrue, 即帧已到期, 直接返回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;
}
完结~撒花

原文地址:https://www.cnblogs.com/Darlietoothpaste/p/9852849.html

时间: 2024-10-18 01:39:26

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

React Fiber源码分析 (介绍)

写了分析源码的文章后, 总觉得缺少了什么, 在这里补一个整体的总结,输出个人的理解~ 文章的系列标题为Fiber源码分析, 那么什么是Fiber,官方给出的解释是: React Fiber是对核心算法的一次重新实现. ummm, 这样说实在是有点泛,详细分析一下 先从开发者角度来看  实际上这次更新对于我们来说影响并不大,只是几个生命周期改变了,新引入的两个生命周期函数 getDerivedStateFromProps,getSnapshotBeforeUpdate 以及在未来 v17.0 版本

Monkey源码分析番外篇之Android注入事件的三种方法比较

原文:http://www.pocketmagic.net/2012/04/injecting-events-programatically-on-android/#.VEoIoIuUcaV 往下分析monkey事件注入源码之前先了解下在android系统下事件注入的方式,翻译一篇国外文章如下. Method 1: Using internal APIs 方法1:使用内部APIs This approach has its risks, like it is always with intern

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

RAMCloud源码分析(三)

RAMCloud源码分析三 概述 mmap系统调用 1 mmap原理 2 mmap使用 HashTable Segment 内存管理 1 Log Metadata 2 Two-level Cleaning 3 Parallel Cleaning 4 Avoiding Cleaner Deadlock 总结 作者:tuyunshan RAMCloud技术交流QQ群:295905581 RAMCloud源码分析(三) 1. 概述 这一部分主要是针对RAMCloud系统中Server进行分析,而本节的

JDK源码分析之String篇

------------------------------String在内存中的存储情况(一下内容摘自参考资料1)----------------------------------- 前提:先了解下什么是声明,什么时候才算是产生了对象实例 其中x并未看到内存分配,变量在使用前必须先声明,再赋值,然后才可以使用.java基础数据类型会用对应的默认值进行初始化 一.首先看看Java虚拟机JVM的内存块及其变量.对象内存空间是怎么存储分配的 1.栈:存放基本数据类型及对象变量的引用,对象本身不存放

Hadoop2源码分析-准备篇

1.概述 我们已经能够搭建一个高可用的Hadoop平台了,也熟悉并掌握了一个项目在Hadoop平台下的开发流程,基于Hadoop的一些套件我们也能够使用,并且能利用这些套件进行一些任务的开发.在Hadoop的应用级别上,我们接着往后面去研究学习,那就是Hadoop的源码了,作为Hadoop开发人员,我们得去学习和研究Hadoop得实现原理,底层框架的设计,编码的实现过程等等,下面就开始我们今天的Hadoop源码分析之旅. 2.准备 在分析源码之前,我们需要准备好分析源码的环境,以及如何去分析(分

【雷电】源码分析(三)-- 游戏背景

Android系统手机屏幕的左上角为坐标系,同时y轴方向与笛卡尔坐标系的y轴方向想反.通过提供的api如getLeft , getTop, getBottom, getRight可以获得控件在parent中的相对位置.同时,也可以获得控件在屏幕中的绝对位置,详细用法可参考android应用程序中获取view的位置 当我们编写一些自定义的滑动控件时,会用到一些api如scrollTo(),scrollBy(),getScrollX(), getScrollY().由于常常会对函数getScroll

jquery1.7.2的源码分析(三)$.Deferred

例子的详细讲解 Filter Resolve 上面的的代码是怎么运行的呢 点击button的先执行 $.Deferred(),得到具有很多方法的defer defer.resolve( 5 ); var doneList = jQuery.Callbacks( "once memory" ), failList = jQuery.Callbacks( "once memory" ), progressList = jQuery.Callbacks( "me

cglib源码分析(三):Class生成策略

cglib中生成类的工作是由AbstractClassGenerator的create方法使用相应的生成策略完成,具体代码如下: private GeneratorStrategy strategy = DefaultGeneratorStrategy.INSTANCE; byte[] b = strategy.generate(this); GeneratorStrategy是一个接口,它负责调用ClassGenerator 的generateClass方法来生成类.DefaultGenera