jjzjj

在react中,fiber链表是怎么遍历的?

修齐治平zzr 2023-09-15 原文
Fiber是对react核心算法的重构,react16以上版本引入了fiber架构,其中的设计思想很值得我们去学习。
那fiber是什么呢?
  • fiber是一个执行单元
  • fiber也是一种数据结构
在没引入fiber之前,React会递归比对VirtualDOM树,找出需要变动的节点,然后同步更新它们。这个过程React称为Reconciliation(协调)。在Reconciliation过程中,React会一直占用着浏览器资源,如果更新节点庞大,那么用户触发的事件可能得不到回馈或者出现卡顿。
fiber的出现则是把庞大的更新节点分割为一个个小的任务单元,浏览器可以在react和响应时间中切换控制权,从而达到优化效果。
fiber的可中断、继续是基于 浏览器的 requestIdleCallback api实现的。目前大多浏览设备每秒刷新60次,requestIdleCallback 函数每一帧会返回这一秒渲染完成后剩余的时间,React就会检查现在还剩多少时间,如果没有时间就将控制权交还浏览器;然后继续进行下一帧的渲染。

虚拟dom转化成fiber后,会有这几个属性,去指向下一个执行单元。

  • child(child指的是第一个子节点而不是所有的子节点)
  • sibling 指的是下一个兄弟节点(弟弟)
  • return 指向父节点
<div id="A1">
  <div id="B1">
     <div id="C1"></div>
     <div id="C2"></div>
  </div>
  <div id="B2"></div>
<div>

这样的jsx如果要转成fiber则会是这个样子

详情可以戳 -> 在react中,虚拟dom是如何转化为fiber链表的?

// element.js
let A1 = { key: 'A1', type: 'div' }
let B1 = { key: 'B1', type: 'div', return: A1 }
let B2 = { key: 'B2', type: 'div', return: A1 }
let C1 = { key: 'C1', type: 'div', return: B1 }
let C2 = { key: 'C2', type: 'div', return: B1 }

A1.child = B1
B1.sibling = B2
B1.child = C1
C1.sibling = C2
module.exports = A1

fiber的遍历顺序是由根节点开始的,优先级 child > sibling > return
所以遍历的先后顺序是

  • A1.child (B1)
  • B1.child (C1)
  • C1.sibling (C2) C1没有child了,就去找它的兄弟
  • C2.return (B1) C2 既没有child,又没有sibling,就去找它的 父节点 return
  • B1.sibling (B2) B1的child已经被遍历过了,就去找它的sibling
  • B2.return (A1)

开始遍历

let rootFiber = require('./element')

let nextUnitOfWork = null

function workLoop() {
  while(nextUnitOfWork) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
  }
}

function performUnitOfWork(currentFiber) { // 返回下一个执行单元
   beginWork(currentFiber)
   if (currentFiber.child) {
    return currentFiber.child
  }
  // 如果能走到这一步,证明遍历到 C1 了
  while(currentFiber) { // 因为此处可能 sibling不一定只有一个,所以要进入while循环
    if(currentFiber.sibling) {
      return currentFiber.sibling
    }
    // 如果没有sibling了,就会到其父节点
    return currentFiber.return
  }
}

function beginWork(currentFiber) {
  // 本章主要记录fiber是怎么遍历的,所以就不去做别的事情啦
  console.log(currentFiber.key)
}

nextUnitOfWork = rootFiber

workLoop()
写到这块是不是少了些什么东西呢?哦对,没有写怎么去让它变成可中断、可恢复的遍历,那就需要祭出上面提出的requestIdleCallback了
我们只需要对workLoop作出小小改动即可
function workLoop(deadline) {
    // timeRemaining() 是指当前渲染完留给react调度的时间
    while ((deadline.timeRemaining() > 1 && nextUnitOfWork) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
    if (!nextUnitOfWork) {
        console.log('render阶段遍历结束')
    } else {
        requestIdleCallback(workLoop, { timeout: 500 });
    }
}

requestIdleCallback(workLoop, { timeout: 500 });
timeout是指的超时时间,意思是,如果超过500ms还没做我的事的话,那你必须放下手头工作,立刻去继续执行workLoop。
这样我们关于fiber链表是怎么遍历的就完成啦!

资料参考: 从零实现React16 Fiber架构与Hooks源码

有关在react中,fiber链表是怎么遍历的?的更多相关文章

  1. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

    我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

  2. ruby - Ruby 中的隐式返回值是怎么回事? - 2

    所以我开始关注ruby​​,很多东西看起来不错,但我对隐式return语句很反感。我理解默认情况下让所有内容返回self或nil但不是语句的最后一个值。对我来说,它看起来非常脆弱(尤其是)如果你正在使用一个不打算返回某些东西的方法(尤其是一个改变状态/破坏性方法的函数!),其他人可能最终依赖于一个返回对方法的目的并不重要,并且有很大的改变机会。隐式返回有什么意义?有没有办法让事情变得更简单?总是有返回以防止隐含返回被认为是好的做法吗?我是不是太担心这个了?附言当人们想要从方法中返回特定的东西时,他们是否经常使用隐式返回,这不是让你组中的其他人更容易破坏彼此的代码吗?当然,记录一切并给出

  3. ruby - 怎么来的(a_method || :other) returns :other only when assigning to a var called a_method? - 2

    给定以下方法:defsome_method:valueend以下语句按我的预期工作:some_method||:other#=>:valuex=some_method||:other#=>:value但是下面语句的行为让我感到困惑:some_method=some_method||:other#=>:other它按预期创建了一个名为some_method的局部变量,随后对some_method的调用返回该局部变量的值。但为什么它分配:other而不是:value呢?我知道这可能不是一件明智的事情,并且可以看出它可能有多么模棱两可,但我认为应该在考虑作业之前评估作业的右侧...我已经在R

  4. ruby-on-rails - 我该怎么办 :remote location validation with CarrierWave? - 2

    我在我的Rails3示例应用程序上使用CarrierWave。我想验证远程位置上传,因此当用户提交无效URL(空白或非图像)时,我不会收到标准错误异常:CarrierWave::DownloadErrorinImageController#createtryingtodownloadafilewhichisnotservedoverHTTP这是我的模型:classPaintingtrue,:length=>{:minimum=>5,:maximum=>100}validates:image,:presence=>trueend这是我的Controller:classPaintingsC

  5. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  6. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  7. ruby - Chef Ruby 遍历 .erb 模板文件中的属性 - 2

    所以这可能有点令人困惑,但请耐心等待。简而言之,我想遍历具有特定键值的所有属性,然后如果值不为空,则将它们插入到模板中。这是我的代码:属性:#===DefaultfileConfigurations#default['elasticsearch']['default']['ES_USER']=''default['elasticsearch']['default']['ES_GROUP']=''default['elasticsearch']['default']['ES_HEAP_SIZE']=''default['elasticsearch']['default']['MAX_OP

  8. ruby - EventMachine - 你怎么知道你是否落后了? - 2

    我正在研究使用EventMachine支持的twitter-streamruby​​gem来跟踪和捕获推文。我对整个事件编程有点陌生。我如何判断我在事件循环中所做的任何处理是否导致我落后?有没有简单的检查方法? 最佳答案 您可以通过使用周期性计时器并打印出耗时来确定延迟。如果您使用的是1秒的计时器,您应该已经过了大约1秒,如果它更长,您就知道您正在减慢react器的速度。@last=Time.now.to_fEM.add_periodic_timer(1)doputs"LATENCY:#{Time.now.to_f-@last}"@

  9. ruby - 如果它是标点符号,我怎么能从字符串中删除最后一个字符,在 ruby​​ 中? - 2

    啊,正则表达式有点困惑。我正在尝试删除字符串末尾所有可能的标点符号:ifstr[str.length-1]=='?'||str[str.length-1]=='.'||str[str.length-1]=='!'orstr[str.length-1]==','||str[str.length-1]==';'str.chomp!end我相信有更好的方法来做到这一点。有什么指点吗? 最佳答案 str.sub!(/[?.!,;]?$/,'')[?.!,;]-字符类。匹配这5个字符中的任何一个(注意,。在字符类中并不特殊)?-前一个字符或组

  10. ruby - 如何遍历 Ruby 中所有正则表达式匹配的字符串? - 2

    我们有一个字符串:“”这个正则表达式://i如何从当前字符串中获取所有匹配项? 最佳答案 "".scan(//)参见scan在ruby​​-docs上 关于ruby-如何遍历Ruby中所有正则表达式匹配的字符串?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/6857852/

随机推荐