当前位置: 首页 > Web前端 > HTML

ReactFiber架构不就是个链表吗

时间:2023-03-28 15:01:53 HTML

看完React源码,相信大家对Fiber会有不同的看法,而我对Fiber最大的看法就是这个东西是一个链表。如果把整个Fiber树作为一个整体来看,源码确实很难看懂,但是如果拆开来看,把每个节点都当成一个独立的单元,思路就很清晰了。接下来我简单说一下,在我看来,React为什么要使用链表数据结构来构建Fiber架构?什么是纤维?可能了解React的人会说,Fiber是一棵虚拟的DOM树;确实如此,但是版本16之前的React也存在。虚拟dom树,为什么要用Fiber代替?众所周知(可能有些小白不知道),在16.8之前,React并没有引入Fiber的概念,Reconciler(协调器)会在mount阶段和update阶段递归mountComponent和updateComponent。这个时候数据是存放在调用栈中的,因为是递归执行的。所以一旦开始,就不能停止,直到递归执行结束;如果此时页面中的节点很多,等到递归结束可能会花费很多时间,用户在这段时间会有卡顿的感觉,这对用户来说是绝对必要的。不能称之为好的体验;所以在16版本之后,React有了异步可中断更新和双缓冲的概念,也就是大家熟知的同步并发模式Concurrentmode,那么这和Fiber有什么关系呢?首先我们看一段关于Fiber节点的React源码this.elementType=null;//this.type=null;//类型this.stateNode=null;//Fiber//关联属性this.return=null;this.child=null;this.sibling=nullthis.index=0;this.ref=null;//工作属性this.pendingProps=pendingProps;this.memoizedProps=null;this.updateQueue=null;吨his.memoizedState=null;this.dependencies=null;this.mode=模式;//效果this.flags=NoFlags;this.subtreeFlags=NoFlags;这个。删除=空;this.lanes=NoLanes;this.childLanes=NoLanes;this.alternate=null;{//注意:执行以下操作是为了避免v8性能悬崖。////将下面的字段初始化为smis并稍后使用双精度值更新它们将导致纤维最终具有单独的形状。//此行为/错误与Object.preventExtension()有关。//幸运的是,这只会影响DEV构建。//不幸的是,它使React对于某些应用程序来说非常慢。//要解决此问题,请使用双精度初始化下面的字段。////在这里了解更多信息://https://github.com/facebook/react/issues/14365//https://bugs.chromium.org/p/v8/issues/detail?id=8538this.actualDuration=Number.NaN;this.actualStartTime=Number.NaN;这个.selfBaseDuration=Number.NaN;this.treeBaseDuration=Number.NaN;//初始化后用smis替换初始的double就可以了。//这不会触发上面提到的性能悬崖,//它简化了其他分析器代码(包括DevTools)。this.actualDuration=0;this.actualStartTime=-1;this.selfBaseDuration=0;this.treeBaseDuration=0;}{//这不是直接使用的,但对于内部调试很方便:this._debugSource=null;this._debugOwner=null;this._debugNeedsRemount=false;this._debugHookTypes=null;if(!hasBadMapPolyfill&&typeofObject.preventExtensions==='function'){Object.preventExtensions(this);}}}可以看到一个FiberNode中有很多属性,我们大致分为三类:静态属性:保存当前Fiber节点的标签、类型等;关联属性:用于连接其他Fiber节点,组成一棵Fiber树;工作属性:保存当前Fiber节点的一个动态工作单元;多个Fiber节点通过关联属性的连接,组成一棵Fiber树;因为每个Fiber节点都是相互独立的,所以Fiber节点是通过指针链接起来的,return指向的是父节点,child指向的是子节点,sibling指向的是兄弟节点;如下以此JSX代码为例

最终,JSX生成的树结构是Fiber树的每个节点都是相互独立的,它们通过指针连接在一起;那我们是不是可以说Fibertree是一个链表,什么是链表,可以参考我的博文《作为前端你是否了解链表这种数据结构?》Fibertree是一个链表。可能现在有漂亮的小伙伴要问了,为什么React会选择链表这种数据结构来搭建Fiber架构呢?这是我考虑的。节点独立性节省操作时间,有利于双重缓存和异步可中断更新操作。节点独立性。不知道有没有大帅哥能说说React的fiber架构把父节点的子节点省下来了,把子节点的返回存储在父节点里。节点是独立的吗?这个漂亮男孩建议你学习通用类型和引用类型;父节点的child存放的是子节点的内存地址,子节点的return存放的是父节点的内存地址,所以并不会占用太多的空间,说白了,他们就一个将节点绑定在一起的关系,但这种关系不是包容性关系;就好比你女朋友是你女朋友你是你,你们是夫妻关系,不是占有关系(不提倡!自由恋爱,独立人格);节省运算时间和单向运算如果fiber树不是链表这样的数据结构而是数组这样的数据结构怎么办?我们都知道数组的存储需要在内存中开辟出一长串有序的内存。如果我删除了中间的一个元素,那么后面的所有元素都会向上移动一个存储空间。如果我现在有1000个节点,我删除了第一个节点,那么接下来的999个节点需要在内存空间中上移一位,这显然是非常耗时的;但是如果是链表的话,我们只需要解除指针的绑定,移动到上一个节点或者下一个节点就可以组成一个新的链表,在时间上是非常有优势的;因为节点之间是相互独立的,我们只需要对指针进行操作,它的操作是单向的。无需双向解绑;我们继续以这个JSX为例如果此时我们要删除类为div1的节点的纤程,它是如何工作的?我们用一张图来解释吧。如图所示,我们只需要将App的child指针改为div2,将div2的返回指针改为App,即可销毁div1和div3;有利于双缓冲和异步中断更新操作异步和可中断更新只能说React为了给用户良好的体验真的下了功夫。在React16之前,React还是采用了原来的同步更新,但是在16之后,React推出了并发模式,就是在同步并发模式下,你的挂载和更新在并发模式下都会变成异步可中断更新。至于React为什么要推出异步可中断更新,可以参考我的文章《重学React之为什么需要Scheduler》现在我们用最直观的浏览器反馈来看看Concurrent模式和Legacy模式的区别。我们来看看Legacy模式下Performance的监控。我们可以看到所有的render阶段的方法都是在同一个Task中完成的。运行时间过长会造成卡顿;让我们看看并发模式。在并发模式下,在React的render阶段,Performance的监控会被分成几个时长为5ms的Task。这都是Scheduler调度器的功劳。因为16之前的React没有Scheduler,所以用的是递归。数据以相同的方式存储在调用堆栈中。一旦递归开始,就无法停止,所以后面有一个Scheduler;并且使用链表的数据结构(Fiber)来存储数据,但是遍历可以很好的打断;让我们看一下并发模式。入口函数functionworkLoopConcurrent(){//执行工作直到Scheduler要求我们让出while(workInProgress!==null&&!shouldYield()){performUnitOfWork(workInProgress);}}sh时可以看到当ouldYield()为true时,workLoopConcurrent方法会中断工作,shouldYield()对应调度器是否需要更新调度状态。doublebuffering和doublebuffering的概念到这里大家应该都清楚了。React在运行时会有两棵Fiber树(mount阶段只有workInProgressFiber树),一棵是当前Fiber树,对应当前显示的内容,另一棵是workInProgressFiber树,对应于正在建设中的纤维树。mount阶段第一次创建会创建一个fiberRootNode根Node,fiberRootNode有一个currentworkunit属性,来回指向Fiber树。在构建workInProgessFiber树时,current指向workInProgressFiber树。此时workInProgessFiber树成为当前Fiber树,当前Fiber树将成为workInProgessFiber树,由于这一切都在内存中完成,所以称为双缓冲;而这一切只是利用链表的灵活指向,不断形成新的链表;总结里没有总结啊~~能不能叫我帅哥?