react fibber

252 阅读7分钟

blog.csdn.net/RaeZhang/ar…

baijiahao.baidu.com/s?id=171337…

****\

React Fiber架构浅析

1.浏览器渲染

为了更好的理解 React Fiber, 我们先简单了解下渲染器进程的内部工作原理。

参考资料:

从内部了解现代浏览器(3)[1]

渲染树构建、布局及绘制[2]

1.1 渲染帧

帧 (frame): 动画过程中,每一幅静止的画面叫做帧。

帧率 (frame per second): 即每秒钟播放的静止画面的数量。

帧时长 (frame running time): 每一幅静止的画面的停留时间。

丢帧 (dropped frame): 当某一帧时长高于平均帧时长。

一般来说浏览器刷新率在60Hz, 渲染一帧时长必须控制在16.67ms (1s / 60 = 16.67ms)。

如果渲染超过该时间, 对用户视觉上来说,会出现卡顿现象,即丢帧 (dropped frame)。

1.2 帧生命周期

86d6277f9e2f0708a5b0d76f6b48f990a801f29a.png

图: 简单描述帧生命周期

简单描述一帧的生命周期:1. 一帧开始。2. 主线程:- Event Handlers: UI交互输入的事件回调, 例如input、click、wheel等。- RAF: 执行requestAnimationFrame回调。- DOM Tree: 解析HTML, 构建DOM Tree, 当JS对DOM有变更会重新触发该流程。- CSS Tree: 构建CSS Tree。至此构建出Render Tree。- Layout: 所有元素的position、size信息。- Paint: 像素填充, 例如颜色、文字、边框等可视部分。- Composite: 绘制的指令信息传到合成线程中。- RequestIdleCallback: 如果此时一帧还有空余时间, 则执行该回调。3. 合成线程:- Raster: 合成线程将信息分块, 并把每块发送给光栅线程, 光栅线程创建位图, 并通知GPU进程刷新这一帧。4. 一帧结束。1.3 丢帧实验

怎么就丢帧了呢?

对于流畅的动画,如果一帧处理时间超过16ms,就能感到页面的卡顿了。

Demo: linjiayu6.github.io/FE-RequestI…

Github: RequestIdleCallback 实验[3]

当用户点击任一按键 A,B,C,因为主线程执行Event Handlers任务,动画因为浏览器不能及时处理下一帧,导致动画出现卡顿的现象。

****\

segmentfault.com/a/119000001…

React Fiber 原理介绍

一、前言

在 React Fiber 架构面世一年多后,最近 React 又发布了最新版 16.8.0,又一激动人心的特性:React Hooks 正式上线,让我升级 React 的意愿越来越强烈了。在升级之前,不妨回到原点,了解下人才济济的 React 团队为什么要大费周章,重写 React 架构,而 Fiber 又是个什么概念。

二、React 15 的问题

在页面元素很多,且需要频繁刷新的场景下,React 15 会出现掉帧的现象。请看以下例子:
claudiopro.github.io/...

\

其根本原因,是大量的同步计算任务阻塞了浏览器的 UI 渲染。默认情况下,JS 运算、页面布局和页面绘制都是运行在浏览器的主线程当中,他们之间是互斥的关系。如果 JS 运算持续占用主线程,页面就没法得到及时的更新。当我们调用setState更新页面的时候,React 会遍历应用的所有节点,计算出差异,然后再更新 UI。整个过程是一气呵成,不能被打断的。如果页面元素很多,整个过程占用的时机就可能超过 16 毫秒,就容易出现掉帧的现象。

针对这一问题,React 团队从框架层面对 web 页面的运行机制做了优化,得到很好的效果。

\

三、解题思路

解决主线程长时间被 JS 运算占用这一问题的基本思路,是将运算切割为多个步骤,分批完成。也就是说在完成一部分任务之后,将控制权交回给浏览器,让浏览器有时间进行页面的渲染。等浏览器忙完之后,再继续之前未完成的任务。

旧版 React 通过递归的方式进行渲染,使用的是 JS 引擎自身的函数调用栈,它会一直执行到栈空为止。而Fiber实现了自己的组件调用栈,它以链表的形式遍历组件树,可以灵活的暂停、继续和丢弃执行的任务。实现方式是使用了浏览器的requestIdleCallback这一 API。官方的解释是这样的:

window.requestIdleCallback()会在浏览器空闲时期依次调用函数,这就可以让开发者在主事件循环中执行后台或低优先级的任务,而且不会对像动画和用户交互这些延迟触发但关键的事件产生影响。函数一般会按先进先调用的顺序执行,除非函数在浏览器调用它之前就到了它的超时时间。

有了解题思路后,我们再来看看 React 具体是怎么做的。

四、React 的答卷

React 框架内部的运作可以分为 3 层:

  • Virtual DOM 层,描述页面长什么样。
  • Reconciler 层,负责调用组件生命周期方法,进行 Diff 运算等。
  • Renderer 层,根据不同的平台,渲染出相应的页面,比较常见的是 ReactDOM 和 ReactNative。

这次改动最大的当属 Reconciler 层了,React 团队也给它起了个新的名字,叫Fiber Reconciler。这就引入另一个关键词:Fiber。

Fiber 其实指的是一种数据结构,它可以用一个纯 JS 对象来表示:

const fiber = {

    stateNode,    // 节点实例

    child,        // 子节点

    sibling,      // 兄弟节点

    return,       // 父节点

}

为了加以区分,以前的 Reconciler 被命名为Stack Reconciler。Stack Reconciler 运作的过程是不能被打断的,必须一条道走到黑:

bVboIrF.png

而 Fiber Reconciler 每执行一段时间,都会将控制权交回给浏览器,可以分段执行:

bVboJj4.png

为了达到这种效果,就需要有一个调度器 (Scheduler) 来进行任务分配。任务的优先级有六种:

  • synchronous,与之前的Stack Reconciler操作一样,同步执行
  • task,在next tick之前执行
  • animation,下一帧之前执行
  • high,在不久的将来立即执行
  • low,稍微延迟执行也没关系
  • offscreen,下一次render时或scroll时才执行

优先级高的任务(如键盘输入)可以打断优先级低的任务(如Diff)的执行,从而更快的生效。

Fiber Reconciler 在执行过程中,会分为 2 个阶段。

bVboJH6.png

  • 阶段一,生成 Fiber 树,得出需要更新的节点信息。这一步是一个渐进的过程,可以被打断。
  • 阶段二,将需要更新的节点一次过批量更新,这个过程不能被打断。

阶段一可被打断的特性,让优先级更高的任务先执行,从框架层面大大降低了页面掉帧的概率。

五、Fiber

Fiber Reconciler 在阶段一进行 Diff 计算的时候,会生成一棵 Fiber 树。这棵树是在 Virtual DOM 树的基础上增加额外的信息来生成的,它本质来说是一个链表。

bVboJHa.png

Fiber 树在首次渲染的时候会一次过生成。在后续需要 Diff 的时候,会根据已有树和最新 Virtual DOM 的信息,生成一棵新的树。这颗新树每生成一个新的节点,都会将控制权交回给主线程,去检查有没有优先级更高的任务需要执行。如果没有,则继续构建树的过程:

bVboJNB.png

如果过程中有优先级更高的任务需要进行,则 Fiber Reconciler 会丢弃正在生成的树,在空闲的时候再重新执行一遍。

在构造 Fiber 树的过程中,Fiber Reconciler 会将需要更新的节点信息保存在Effect List当中,在阶段二执行的时候,会批量更新相应的节点。

六、总结

本文从 React 15 存在的问题出发,介绍 React Fiber 解决问题的思路,并介绍了 Fiber Reconciler 的工作流程。从Stack Reconciler到Fiber Reconciler,源码层面其实就是干了一件递归改循环的事情,日后有机会的话,我再结合源码作进一步的介绍。

mp.weixin.qq.com/s/wNIsRcP1K…

React理念

用 JavaScript 构建快速响应的大型 Web 应用程序。

\

速度快

  • 使用PureComponent或React.memo构建组件\

  • 使用shouldComponentUpdate生命周期钩子\

  • 渲染列表时使用key\

  • 使用useCallback和useMemo缓存函数和变量\

\

响应自然

将人机交互研究的结果整合到真实的 UI 中。

\

  • 同步的更新变为可中断的异步更新\

  • 在浏览器每一帧的时间中,预留一些时间给JS线程,React利用这部分时间更新组件(可以看到,在源码中,预留的初始时间是5ms)\

  • 当预留的时间不够用时,React将线程控制权交还给浏览器使其有时间渲染UI,React则等待下一帧时间到来继续被中断的工作。\

\