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

栏目: IOS · Android · 发布时间: 5年前

内容简介:React Fiber是React在V16版本中的大更新,利用了闲余时间看了一些源码,做个小记录~1.调用

系列文章

前言

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

流程图

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

源码分析

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

总结

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


以上所述就是小编给大家介绍的《React Fiber源码分析 第三篇(异步状态)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Dive Into Python 3

Dive Into Python 3

Mark Pilgrim / Apress / 2009-11-6 / USD 44.99

Mark Pilgrim's Dive Into Python 3 is a hands-on guide to Python 3 (the latest version of the Python language) and its differences from Python 2. As in the original book, Dive Into Python, each chapter......一起来看看 《Dive Into Python 3》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具