React引入fiber架构的主要原因是为了实现更好的异步渲染和更高效的任务调度,fiber架构使得React能够更细粒度的控制和中断渲染过程,以便更好地响应用户交互、实现懒加载等功能,Vue在设计上采用了不同的策略,因此不需要类似于fiber的架构
异步渲染和任务优先级:React的fiber架构使得实现异步渲染和任务优先级变得更加容易,这对于复杂的用户界面和大规模应用中的性能优化非常重要,React可以通过中断和恢复渲染过程,根据任务的优先级调度渲染工作,从而更好地响应用户输入和满足实时性要求
更好的中断和恢复机制:fiber架构提供了一种更灵活的中断和恢复机制,允许React在渲染过程中暂停、中断,然后根据优先级恢复。这使得React能够更好地处理复杂的渲染逻辑,并在需要时放弃低优先级的工作
增量更新:fiber允许React实现增量更新,即只更新变化的部分而不必重新夏然整个组件树,这对于提高渲染性能和减少不必要的工作非常有帮助
更新过程的可控性主要体现在下面几个方面:
在React fiber机制中,它采用了化整为零的思想,将调和阶段递归遍历VDOM这个大任务分成若干小任务,每个任务只负责一个节点的处理
workInProgress tree
workInProgress代表当前正在执行更新的fiber树,在render或者是setState后,会构建一颗fiber树,也就是workInProgress tree,这棵树在构建每一个节点的时候会收集当前节点的副作用,整棵树构建完成后,会形成一条完整的副作用链
currentFiber tree
currentFiber表示上次渲染构建的fiber树,在每一次更新完成后workInprogress会赋值给currentFiber,在新一轮更新时workInProgress tree再重新构建,新workInProgress的节点,通过alternate属性和currentFiber的节点建立联系
在新workInProgress的创建过程中,会同currentFiber的对应节点进行diff比较,收集副作用,同时也会复用和currentFiber对应的节点对象,减少新创建对象带来的开销,也就是说无论是创建还是更新、挂起、恢复以及终止操作都是发生在workInProgress tree创建过程中的,workInProgress tree构建过程其实就是循环的执行任务和创建下一个任务
挂起
当第一个小任务完成后,先判断这一帧是否还有空闲时间,没有就挂起下一个任务的执行,记住当前挂起的节点,让出控制权给浏览器执行更高优先级的任务
恢复
在浏览器渲染完一帧后灭阶段当前帧是否有剩余时间,如果有就恢复执行之前挂起的任务,如果没有任务需要处理,代表调和阶段完成,可以开始进入渲染阶段
使用前面提到的RIC(RequestIdleCallback)浏览器原生API,React源码中为了兼容低版本浏览器,对该方法进行了Polyfill
答案是在前面提到的链表,在React fiber中每一个任务其实就是在处理一个fiberNode对象,然后又生成下一个任务需要处理的fiberNode
终止
其实并不是每次更新都会走到提交阶段,当在调和过程中触发了新的更新,在执行下一个任务的时候,判断是否有优先级更高的执行任务,如果有就终止原来将要执行的任务,开始新的workInProgress tree的构建过程,开始新的更新流程,这样可以避免重复更新操作,这也是在React16以后生命周期componentWIllMount有可能会执行多次的原因
fiber除了通过挂起、恢复和终止来控制更新外,还给每一个任务分配了优先级,具体点就是在创建或者更新fiberNode的时候,通过算法给每个任务分配一个到期时间(expirationTime),在每个任务执行的时候除了判断剩余事件,如果当前处理节点已经过期,那么无论现在是否有空闲时间都必须执行该任务,过期时间的大小还代表着任务的优先级
任务在执行过程中顺便收集了每个fiberNode的副作用链表,有了它,在接下来的渲染阶段就是通过遍历副作用链表完成DOM更新,这里需要注意,更新真实DOM的这个动作是一气呵成的,不能中断,不然会造成视觉上的不连贯。
fiber是英文含义中的纤维,他是比线程更细的线,比线程控制的更精密的执行模型,在广义计算机科学概念中,fiber又是一种协作的编程模型,帮助开发者用一种「即模块化又协作化」的方式来编排代码
在React中就是React16实现的一套新的更新机制,让React的更新过程变得可控,避免了之前采用递归需要一气呵成影响性能的做法
把一个耗时长的任务分成很多小片,每一个小片的运行时间很短,算然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务依然又运行的机会
fiber把更新过程碎片化,每执行完一段更新过程,就把控制权交还给React负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续去更新,如果有紧急任务,那就去做紧急任务
基于栈的Reconciler,浏览器引擎会从执行栈的顶端开始执行,执行完毕就弹出当前执行上下文,开始执行下一个函数,直到执行栈被清空才会停止,然后将执行权交还给浏览器,由于React将页面视图视作一个个函数执行的结果,每一个页面往往由多个视图组成,这就意味着多个函数的调用
如果一个页面足够复杂,形成的函数调用栈就会很深,每一次更新,执行栈需要一次性执行完成,中途不能干其他的式,只能一心一意,结合前面提到的浏览器刷新率,JS一直执行,浏览器得不到控制权,就不能即时开始下一帧的绘制,如果这个事件超过16ms,当页面由动画效果需求时,动画因为浏览器不能及时绘制下一帧,这时动画就会出现卡顿,不仅如此,因为时间响应代码是在每一帧开始的时候执行,如果不能即时绘制下一帧,事件响应也会延迟
在Fiber中使用链表遍历的方法替代了之前的栈递归方案,在React16中使用了大量的链表
副作用链表:
状态更新单链表
链表是一种简单高笑的数据结构,它在当前节点中保存着下一个节点的指针,遍历的时候,通过操作指针找到以一个元素
链表相比顺序结构数据格式的好处就是:
但是链表也不是完美的,他的缺点就是:
React用空间换时间,更高效的操作可以方便根据优先级进行操作,同时可以根据当前节点找到其他节点,在下面提到的挂起和恢复过程中起到了关键作用