正文

看了React源码之后相信大家都会对Fiber有自己不同的见解,而我对Fiber最大的见解就是这玩意儿就是个链表。如果把整个Fiber树当成一个整体确实有点难理解源码,但是如果把它拆开了,将每个节点都看成一个独立单元却能得到一个很清晰的思路,接下来我就简单几点讲讲,我所认为的为什么React要用链表这种数据结构来构建Fiber架构

什么是Fiber

可能了解过React的靓仔就要说了,Fiber就是一个虚拟dom树;确实如此,但是16版本之前的React也存在虚拟dom树,为什么要用Fiber替代呢?

众所周知(可能有靓仔不知道),16.8之前React还没引入Fiber概念,Reconciler(协调器) 会在mount阶段与update阶段循环递归mountComponentupdateComponent,此时数据存储在调用栈当中,因为是递归执行,所以一当开始便无法停止直到递归执行结束;如果此时页面中的节点非常多我们要等到递归结束可能要耗费大量的时间,而且在此之间用户会觉得卡顿,这对用户来说绝对称不上是好的体验;

因此在16版本之后React有了异步可中断更新双缓存的概念,也就是我们熟知的同步并发模式Concurrent模式,那么这些跟Fiber有什么关系呢?

Fiber节点React源码

首先我们来看一段关于Fiber节点的React源码

function FiberNode(tag, pendingProps, key, mode) {
  // Instance
  //静态属性
  this.tag = tag;//
  this.key = key;
  this.elementType = null;//
  this.type = null;//类型
  this.stateNode = null; // Fiber
  //关联属性
  this.return = null;
  this.child = null;
  this.sibling = null
  this.index = 0;
  this.ref = null;
  //工作属性
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;
  this.mode = mode; // Effects
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.deletions = null;
  this.lanes = NoLanes;
  this.childLanes = NoLanes;
  this.alternate = null;
  {
    // Note: The following is done to avoid a v8 performance cliff.
    //
    // Initializing the fields below to smis and later updating them with
    // double values will cause Fibers to end up having separate shapes.
    // This behavior/bug has something to do with Object.preventExtension().
    // Fortunately this only impacts DEV builds.
    // Unfortunately it makes React unusably slow for some applications.
    // To work around this, initialize the fields below with doubles.
    //
    // Learn more about this here:
    // https://github.com/facebook/react/issues/14365
    // https://bugs.chromium.org/p/v8/issues/detail?id=8538
    this.actualDuration = Number.NaN;
    this.actualStartTime = Number.NaN;
    this.selfBaseDuration = Number.NaN;
    this.treeBaseDuration = Number.NaN; // It's okay to replace the initial doubles with smis after initialization.
    // This won't trigger the performance cliff mentioned above,
    // and it simplifies other profiler code (including DevTools).
    this.actualDuration = 0;
    this.actualStartTime = -1;
    this.selfBaseDuration = 0;
    this.treeBaseDuration = 0;
  }
  {
    // This isn't directly used but is handy for debugging internals:
    this._debugSource = null;
    this._debugOwner = null;
    this._debugNeedsRemount = false;
    this._debugHookTypes = null;
    if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
      Object.preventExtensions(this);
    }
  }
}

可以看到在一个FiberNode当中存在很多属性,我们大体将他们分为三类:

  • 静态属性:保存当前Fiber节点的 标签,类型等;
  • 关联属性:用于连接其他Fiber节点形成Fiber树;
  • 工作属性:保存当前Fiber节点的动态工作单元;

而多个Fiber节点之间正是通过关联属性的连接形成一个Fiber树;因为每一个Fiber节点都是相互独立的,因此Fiber节点之间通过指针指向的方式产生联系,return指向的是父级节点,child指向的是子节点,sibling指向的是兄弟节点;

如下列这段JSX代码为例

<div className="App">
  <div className='div1'>
    <div className='div2'>
    </div>
  </div>
  <div className='div3'>
  </div>
</div>

最终该JSX产生的树结构为

Fiber树的每个节点都是相互独立的,利用指针指向让他们关联在一起;那么我们是不是可以说Fiber树就是一个链表,关于什么是链表,可以参考我这篇博文 《作为前端你是否了解链表这种数据结构?》

Fiber树是链表

可能现在就有靓仔要问了,为什么React要选用链表这种数据结构搭建Fiber架构

我是这么考虑的

  • 节点独立
  • 节省操作时间
  • 利于双缓存与异步可中断更新操作

节点独立

不知道有没有靓仔会说ReactFiber架构拿父节点的child存子节点拿子节点的return存父节点怎么就节点独立了呢?这位靓仔贫道建议你再去学一下一般类型和引用类型;父节的child存的是子节点的内存地址,子节点的return存的是父节点的内存地址,因此并不会占用太多空间,说白了他们只是有一层关系将节点绑定在一起,但是这层关系并不是包含关系;就比如你女朋友是你女朋友,你是你一样,你们是情侣关系,并不是占有关系(不提倡啊!自由恋爱,人格独立);

节省操作时间与单向操作

如果Fiber树并不是链表这种数据结构而是数组这种数据结构会怎么样呢?我们都知道数组的存储需要在内存中开辟一长串有序的内存,如果我把中间的某个元素删除,那么后面的所有元素都要向上移动一个存储空间,如果现在我有1000个节点,我把第一个节点删了,那么后面的999个节点都需要在内存空间上向上移动一位,这显然是非常消耗时间的;但是如果是链表的话我们只需要将指针解绑,移动到上一位节点或者下一节点就能形成一个新的链表,这在时间上来说是非常有优势的;因为是 节点间相互独立因此我们仅仅只需要对指针进行操作并且它的操作是单向的我们不需要进行双向解绑;

我们继续以这段JSX为例

<div className="App">
  <div className='div1'>
    <div className='div2'>
    </div>
  </div>
  <div className='div3'>
  </div>
</div>

如果此时我们要将class为div1的节点删除fiber是如何操作的?我们用图来解释

由图所示,我们只需要将App的child指针改为div2,将div2的return指针改为App即可,然后我们便可以对div1与div3进行销毁;

利于双缓存与异步可中断更新操作

异步可中断更新

我只能说React为了给用户良好的使用感受确实是下足了功夫,在React16之前React还采取着原始的同步更新,但是在在16之后React推出了concurrent模式也就是同步并发模式,在concurrent模式下你的mountupdate都将成为异步可中断更新,至于react为什么要推出异步可中断更新可参考我这篇文章 《重学React之为什么需要Scheduler》

现在我们用最直观的浏览器反馈来看一下Concurrent模式Legacy模式的区别

我们看看Legacy模式下的Performance的监听

可以看到所有的render阶段方法都在同一个Task完成,如果运行时间过长将会造成卡顿;

我们再看Concurrent模式下的Performance的监听

concurrent模式下会react的render阶段会被分为若干个时长为5ms的Task

这一切归功于Scheduler调度器的功劳,因为16之前的React没有Scheduler所以采用的是所以采用的是递归的方式将数据存储在调用栈当中,递归一旦开始便无法停止,所以后来有了Scheduler;而采用链表这种数据结构(Fiber)存储数据却能很好的中断遍历;我们来看看Concurrent模式下的入口函数

function workLoopConcurrent() {
  // Perform work until Scheduler asks us to yield
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

可以看到当shouldYield() 为true时workLoopConcurrent方法将会中断工作,而shouldYield() 对应的正是scheduler是否需要更新调度的状态

双缓存

双缓存的概念在座的靓仔应该都清楚,React在运行时会有两棵Fiber树mount阶段只有workInProgress Fiber树), 一颗是current Fiber树,对应当前展示的内容,一颗是workInProgress Fiber树对应的是正在构建的Fiber树,在mount阶段的首次创建会创建一个fiberRootNode的根节点,fiberRootNode 有一个current工作单元属性,来回指向Fiber树,当workInProgess Fiber树构建完成之后current就指向workInprogress Fiber树,此时workInProgess Fiber树变为current Fiber树,而current Fiber树将变为workInProgess Fiber树,由于这一切都是在内存中进行的,所以称之为双缓存;

而这一切刚好运用了链表的灵活指向,不断形成一个新的链表;

以上就是React Fiber 链表操作原理详解的详细内容,更多关于React Fiber 链表的资料请关注Devmax其它相关文章!

React Fiber 链表操作及原理示例详解的更多相关文章

  1. ios – React native链接到另一个应用程序

    如果是错误的,有人知道如何调用正确的吗?

  2. ios – React Native – 在异步操作后导航

    我正在使用ReactNative和Redux开发移动应用程序,我正面临着软件设计问题.我想调用RESTAPI进行登录,如果该操作成功,则导航到主视图.我正在使用redux和thunk所以我已经实现了异步操作,所以我的主要疑问是:我应该把逻辑导航到主视图?我可以直接从动作访问导航器对象并在那里执行导航吗?.我对组件中的逻辑没有信心.似乎不是一个好习惯.有没有其他方法可以做到这一点?

  3. 在ios中使用带有React Native(0.43.4)的cocoapods的正确方法是什么?

    我已经挖掘了很多帖子试图使用cocoapods为本地ios库设置一个反应原生项目,但我不可避免地在#import中找到了丢失文件的错误.我的AppDelegate.m文件中的语句.什么是使用反应原生的可可豆荚的正确方法?在这篇文章发表时,我目前的RN版本是0.43.4,而我正在使用Xcode8.2.1.这是我的过程,好奇我可能会出错:1)

  4. ios – React Native WebView滚动行为无法按预期工作

    如何确保滚动事件的行为与ReactNative应用程序中的浏览器相同?

  5. ios – React Native – BVLinearGradient – 找不到’React/RCTViewManager.h’文件

    谢谢.解决方法几天前我遇到了完全相同的问题.问题是在构建应用程序时React尚未链接.试试这个:转到Product=>Scheme=>管理方案…=>点击你的应用程序Scheme,然后点击Edit=>转到Build选项卡=>取消选中ParallelizeBuild然后点击标志添加目标=>搜索React,选择第一个名为React的目标,然后单击Add然后在目标列表中选择React并将其向上拖动到该列表中的第一个.然后转到Product=>再次清理并构建项目.这应该有所帮助.

  6. ios – React Native – NSNumber无法转换为NSString

    解决方法在你的fontWeight()函数中也许变成:

  7. ios – React native error – react-native-xcode.sh:line 45:react-native:command not found命令/ bin/sh失败,退出代码127

    尝试构建任何(新的或旧的)项目时出现此错误.我的节点是版本4.2.1,react-native是版本0.1.7.我看过其他有相同问题的人,所以我已经更新了本机的最新版本,但是我仍然无法通过xcode构建任何项目.解决方法要解决此问题,请使用以下步骤:>使用节点版本v4.2.1>cd进入[你的应用]/node_modules/react-native/packager>$sh./packager.s

  8. 反应原生 – 如何通过Xcode构建React Native iOS应用程序到设备?

    我试图将AwesomeProject应用程序构建到设备上.构建成功并启动屏幕显示,但后来我看到一个红色的“无法连接到开发服务器”屏幕.它表示“确保节点服务器正在运行–从Reactroot运行”npmstart“.看起来节点服务器已经运行,因为当我做npm启动时,我收到一个EADDRINUSE消息,表示该端口已经在使用.解决方法从设备访问开发服务器您可以使用开发服务器快速迭代设备.要做到这一点,你的

  9. 静音iOS推送通知与React Native应用程序在后台

    我有一个ReactNative应用程序,我试图获得一个发送到JavaScript处理程序的静默iOS推送通知.我看到的行为是AppDelegate中的didReceiveRemoteNotification函数被调用,但是我的JavaScript中的处理程序不会被调用,除非应用程序在前台,或者最近才被关闭.我很困惑的事情显然是应用程序正在被唤醒,并且它的didReceiveRemoteNotifi

  10. 如何为iOS的React Native设置分析

    所以我已经完成了一个针对iOS的ReactNative项目,但是我想在其中分析.我尝试了react-native-google-analytics软件包,但是问题阻止了它的正常工作.此外,react-native-cordova-plugin软件包只适用于Android,因此插入Cordova插件进行分析的能力现在已成为问题.我也没有Swift/ObjectiveC的经验,所以将完全失去GA的插入.有没有人有任何建议如何连接GoogleAnalytics的ReactNativeforiOS?

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部