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;

结语

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


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

查看所有标签

猜你喜欢:

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

人人都是产品经理

人人都是产品经理

苏杰 / 电子工业出版社 / 2012-6 / 45.00元

本书为《人人都是产品经理》的升级版,是写给“1到3岁的产品经理”的书,适合刚入门的产品经理、产品规划师、需求分析师,以及对做产品感兴趣的学生,用户体验、市场运营、技术部门的朋友们,特别是互联网、软件行业。作为一名“4岁的产品经理”,作者讲述了过去3年的经历与体会,与前辈们的书不同,本书就像你走到作者身边,说“嗨,哥们!晚上有空吃个饭吗,随便聊聊做产品的事吧”,然后作者说“好啊”。 书名叫“......一起来看看 《人人都是产品经理》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具