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

栏目: 服务器 · 发布时间: 5年前

内容简介:React Fiber是React在V16版本中的大更新,利用了闲余时间看了一些源码,做个小记录~如果有错误,请轻喷

系列文章

前言

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

如果有错误,请轻喷

流程图

流程图1

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

流程图2

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

源码分析

1. scheduleRootUpdate 这个函数主要执行了两个操作 1个是创建更新 createUpdate 并放到更新队列 enqueueUpdate , 1个是执行 sheculeWork 函数

function scheduleRootUpdate(current$$1, element, expirationTime, callback) {
var update = createUpdate(expirationTime);
  update.payload = { element: element };

  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    update.callback = callback;
  }
  enqueueUpdate(current$$1, update);

  scheduleWork(current$$1, expirationTime);
  return expirationTime;
}

2.先从 createUpdate 函数分析, 他直接返回了一个包含了更新信息的对象

function createUpdate(expirationTime) {
  return {
    // 优先级
    expirationTime: expirationTime,
    // 更新类型
    tag: UpdateState,
    // 更新的对象
    payload: null,
    callback: null,
    // 指向下一个更新
    next: null,
    // 指向下一个更新effect
    nextEffect: null
  };
}

3.接着更新 payload和callback 属性, payload 即为更新的对象, 然后执行 enqueuUpdateenqueueUpdate 相对比较容易理解, 不过里面有一注释挺重要

Both queues are non-empty. The last update is the same in both lists, because of structural sharing. So, only append to one of the lists 意思是alternate的updateQueue和fiber的updateQueue是同一个对象引用,这里会在createWorkInProcess提到

往下走就是重要的 scheduleWork , 它是 render 阶段真正的开始

function scheduleWork(fiber, expirationTime) {
  // 更新优先级
  var root = scheduleWorkToRoot(fiber, expirationTime);
  ...if (!isWorking && nextRenderExpirationTime !== NoWork && expirationTime < nextRenderExpirationTime) {
    // This is an interruption. (Used for performance tracking.) 如果这是一个打断原有更新的任务, 先把现有任务记录
    interruptedBy = fiber;
    resetStack();
  }
  // 设置下一个操作时间nextExpirationTimeToWorkOn
  markPendingPriorityLevel(root, expirationTime);
  if (
  // If we're in the render phase, we don't need to schedule this root
  // for an update, because we'll do it before we exit...
  !isWorking || isCommitting$1 ||
  // ...unless this is a different root than the one we're rendering.
  nextRoot !== root) {
    var rootExpirationTime = root.expirationTime;
    requestWork(root, rootExpirationTime);
  }
  ...
}

4. scheduleWork 先执行一个 scheduleWorkToRoot 函数, 该函数主要是更新其 expirationTime 以及上层 fiberchildrenExpirationTime

function scheduleWorkToRoot(fiber, expirationTime) {
  // Update the source fiber's expiration time
  if (fiber.expirationTime === NoWork || fiber.expirationTime > expirationTime) {
    fiber.expirationTime = expirationTime;
  }
  var alternate = fiber.alternate;
  if (alternate !== null && (alternate.expirationTime === NoWork || alternate.expirationTime > expirationTime)) {
    alternate.expirationTime = expirationTime;
  }
  // 如果是HostRoot 即直接返回
  var node = fiber.return;
  if (node === null && fiber.tag === HostRoot) {
    return fiber.stateNode;
  }
  // 若子fiber中有更新, 即更新其childrenExpirationTime
  while (node !== null) {
    ...
  }
  return null;
}

5.接着会执行一个 markPendingPriorityLevel 函数,这个函数主要是更新 root 的最高优先级和最低优先级( earliestPendingTime和lastestPendingTime; ), 同时设置下一个执行操作的时间 nextExpirationTimeToWorkOn (即root中具有最高优先级的fiber的expirationTime),关于这个函数的 latestSuspendedTime ;以后再说

最后scheduleWork会执行requestWork

function requestWork(root, expirationTime) {
  addRootToSchedule(root, expirationTime);
  if (isRendering) {
    // rendering状态,直接返回
    return;
  }

  if (isBatchingUpdates) {
    // isBatchingUpdates, 直接返回。 react的state更新是会合并的
    ...return;
  }

  // TODO: Get rid of Sync and use current time?
  if (expirationTime === Sync) {
    // 执行同步
    performSyncWork();
  } else {
    // 异步, 暂不分析
    scheduleCallbackWithExpirationTime(root, expirationTime);
  }
}

6. requestWork 会先执行 addRootToSchedule ,由函数名称可知其作用,将 root 加到 schedule , 即设置 firstScheduledRootlastScheduledRoot 以及他们的 nextScheduleRoot 属性,说白了就是一个闭环链式结构 first => next => next => last(next => first) , 同时更新 rootexpirationTime 属性

function addRootToSchedule(root, expirationTime) {
   // root尚未开始过任务 将root加到schedule
  if (root.nextScheduledRoot === null) {
    ...
  } else {
    // root已经开始执行过任务, 更新root的expirationTime
    var remainingExpirationTime = root.expirationTime;
    if (remainingExpirationTime === NoWork || expirationTime < remainingExpirationTime) {
      root.expirationTime = expirationTime;
    }
  }
}

7.接着 requestWork 会判断是否正在渲染中,防止重入。剩余的工作将安排在当前渲染批次的末尾,如果正在渲染直接返回后, 因为已经把 root 加上到 Schedule 里面了,依然会把该 root 执行

同时判断是否正在 batch update , 这里留到分析 setState 的时候说, 最后根据异步或者同步执行不同函数, 此处执行同步 performSyncWork(),performSyncWork 直接执行 performWork(Sync, null) ;

function performWork(minExpirationTime, dl) {
  deadline = dl;
  // 找出优先级最高的root
  findHighestPriorityRoot();

  if (deadline !== null) {
    // ...异步
  } else {
    // 循环执行root任务
    while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || minExpirationTime >= nextFlushedExpirationTime)) {
      performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, true);
      findHighestPriorityRoot();
    }
  }
  ...
  // If there's work left over, schedule a new callback.
  if (nextFlushedExpirationTime !== NoWork) {
    scheduleCallbackWithExpirationTime(nextFlushedRoot, nextFlushedExpirationTime);
  }
 ...
}

8. performWork 首先执行 findHighestPriorityRoot 函数。 findHighestPriorityRoot 函数主要执行两个操作, 一个是判断当前 root 是否还有任务,如果没有, 则从 firstScheuleRoot 链中移除。 一个是找出优先级最高的 root 和其对应的优先级并赋值给

nextFlushedRootnextFlushedExpirationTime

function findHighestPriorityRoot() {
  var highestPriorityWork = NoWork;
  var highestPriorityRoot = null;
  if (lastScheduledRoot !== null) {
    var previousScheduledRoot = lastScheduledRoot;
    var root = firstScheduledRoot;
    while (root !== null) {
      var remainingExpirationTime = root.expirationTime;
      if (remainingExpirationTime === NoWork) {
         // 判断是否还有任务并移除
      } else {
         // 找出最高的优先级root和其对应的优先级
      }
    }
  }
  // 赋值
  nextFlushedRoot = highestPriorityRoot;
  nextFlushedExpirationTime = highestPriorityWork;
}

9.紧着, performWork 会根据传入的参数dl来判断进行同步或者异步操作, 这里暂不讨论异步,

while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || minExpirationTime >= nextFlushedExpirationTime)) {
      performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, true);
      findHighestPriorityRoot();
    }

10.接着, 会进行 performWorkOnRoot 函数, 并传入优先级最高的 root 和其对应的 expirationTime 以及一个 true 作为参数, performWorkOnRoot 函数的第三个参数 isExpired 主要是用来判断是否已超过执行时间, 由于进行的是同步操作, 所以默认超过

performWorkOnRoot函数会先将 rendering 状态设为 true , 然后判断是否异步或者超时进行操作

function performWorkOnRoot(root, expirationTime, isExpired) {
  // 将rendering状态设为true
  isRendering = true;

  // Check if this is async work or sync/expired work.
  if (deadline === null || isExpired) {
    // Flush work without yielding.
    // 同步
    var finishedWork = root.finishedWork;
    if (finishedWork !== null) {
      // This root is already complete. We can commit it.
      completeRoot(root, finishedWork, expirationTime);
    } else {
      root.finishedWork = null;
      // If this root previously suspended, clear its existing timeout, since
      // we're about to try rendering again.
      var timeoutHandle = root.timeoutHandle;
      if (enableSuspense && timeoutHandle !== noTimeout) {
        root.timeoutHandle = noTimeout;
        // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
        cancelTimeout(timeoutHandle);
      }
      var isYieldy = false;
      renderRoot(root, isYieldy, isExpired);
      finishedWork = root.finishedWork;
      if (finishedWork !== null) {
        // We've completed the root. Commit it.
        completeRoot(root, finishedWork, expirationTime);
      }
    }
  } else {
    // Flush async work.异步操作
    ......
    }
  }

  isRendering = false;
}

11. renderRoot 的产物会挂载到 rootfinishWork 属性上, 首先 performWorkOnRoot 会先判断 rootfinishWork 是否不为空, 如果存在的话则直接进入 commit 的阶段, 否则进入到 renderRoot 函数, 设置 finishWork 属性

renderRoot有三个参数, renderRoot(root, isYieldy, isExpired) , 同步状态下 isYield 的值是 false,

renderRoot先将 isWorking 设为 true ,

renderRoot会先判断是否是一个从新开始的 root , 是的话会重置各个属性

首先是 resetStach() 函数, 对原有的进行中的 root 任务中断, 进行存储

紧接着将 nextRootnextRendeExpirationTime 重置, 同时创建第一个 nextUnitOfWork , 也就是一个工作单元

这个 nextUnitOfWork 也是一个 workProgress , 也是 root.current的alternater 属性, 而它的 alternate 属性则指向了 root.current , 形成了一个双缓冲池

if (expirationTime !== nextRenderExpirationTime || root !== nextRoot || nextUnitOfWork === null) {
    // 判断是否是一个从新开始的root
    resetStack();
    nextRoot = root;
    nextRenderExpirationTime = expirationTime;
    nextUnitOfWork = createWorkInProgress(nextRoot.current, null, nextRenderExpirationTime);
    root.pendingCommitExpirationTime = NoWork;
    ....
    ....
  }

12.接着执行 wookLoop(isYield) 函数, 该函数通过循环执行, 遍历每一个 nextUniOfWork ,

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

13. performUnitOfWork 先 获取 参数的 alaernate 属性, 赋值给 current ,根据注释的意思, workInProgress 是作为一个代替品存在来操作, 然后会执行下面这个语句

next = beginWork(current$$1, workInProgress, nextRenderExpirationTime);

14. beginWork 主要根据 workInprogresstag 来做不同的处理, 并返回其 child , 也就是下一个工作单元 如<div><p></p><div>, div作为一个工作单元, 处理完后就返回工作单元p, 同时收集他们的 effect

next 存在, 则返回到 workLoop 函数继续循环, 若不存在, 则执行 completeUnitOfWork(workInProgress) 函数

completeUnitOfWork函数, 会判断是否有 sibiling , 有则直接返回赋值给 next , 否则判断父 fiber 是否有 sibiling , 一直循环到最上层父 fiber为null , 执行的同时会把 effect 逐级传给父 fiber

这个时候函数执行完毕, 会返回到 renderRoot 函数, renderRoot 函数继续往下走

首先将 isWorking = false ;执行, 然后会判断 nextUnitWork 是否为空, 否的话则将 root.finishWork 设为空(异步, 该任务未执行完)并结束函数

isWorking = false;
if (nextUnitOfWork !== null) {
 onYield(root);
 return;
}

重置nextRoot等

nextRoot = null;
interruptedBy = null;

赋值finishWork

var rootWorkInProgress = root.current.alternate;
onComplete(root, rootWorkInProgress, expirationTime);
function onComplete(root, finishedWork, expirationTime) {
  root.pendingCommitExpirationTime = expirationTime;
  root.finishedWork = finishedWork;
}

15.返回到 performWorkOnRoot 函数, 进入 commit 阶段, 将 rending 状态设为 false ,返回到 performWork 函数, 继续进入循环执行 root , 直到所有 root 完成

重置各个状态量, 如果还存在 nextFlushedExpirationTime 不为空, 则进行 scheduleCallbackWithExpirationTime 函数异步操作

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;

结语

以上就是同步模式下的源码分析~


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

Web Data Mining

Web Data Mining

Bing Liu / Springer / 2011-6-26 / CAD 61.50

Web mining aims to discover useful information and knowledge from Web hyperlinks, page contents, and usage data. Although Web mining uses many conventional data mining techniques, it is not purely an ......一起来看看 《Web Data Mining》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具