react fiber
what is react fiber
React Fiber is to increase its suitability for areas like animation, layout, and gestures. Its headline feature is incremental rendering: the ability to split rendering work into chunks and spread it out over multiple frames.
简单点说,Fiber 就是 React 16 实现的一套新的更新机制,让 React 的更新过程变得可控,避免了之前一竿子递归到底影响性能的做法。
关于 Fiber 你需要知道的基础知识
浏览器刷新率(帧)
页面的内容都是一帧一帧绘制出来的,浏览器刷新率代表浏览器一秒绘制多少帧。目前浏览器大多是 60Hz(60帧/s),每一帧耗时也就是在 16ms 左右。原则上说 1s 内绘制的帧数也多,画面表现就也细腻。那么在这一帧的(16ms) 过程中浏览器又干了啥呢?
[]
- 接受输入事件
- 执行事件回调
- 开始一帧
- 执行 RAF (RequestAnimationFrame)
- 页面布局,样式计算
- 渲染
- 执行 RIC (RequestIdelCallback)
第七步的 RIC 事件不是每一帧结束都会执行,只有在一帧的 16ms 中做完了前面 6 件事儿且还有剩余时间,才会执行。这里提一下,如果一帧执行结束后还有时间执行 RIC 事件,那么下一帧需要在事件执行结束才能继续渲染,所以 RIC 执行不要超过 30ms,如果长时间不将控制权交还给浏览器,会影响下一帧的渲染,导致页面出现卡顿和事件响应不及时。
JS 原生执行栈
React Fiber 出现之前,React 通过原生执行栈递归遍历 VDOM 浏览器引擎会从执行栈的顶端开始执行,执行完毕就弹出当前执行上下文,开始执行下一个函数,直到执行栈被清空才会停止。然后将执行权交还给浏览器。由于 React 将页面视图视作一个个函数执行的结果。每一个页面往往由多个视图组成,这就意味着多个函数的调用。
如果一个页面足够复杂,形成的函数调用栈就会很深。每一次更新,执行栈需要一次性执行完成,中途不能干其他的事儿,只能"一心一意"。结合前面提到的浏览器刷新率,JS 一直执行,浏览器得不到控制权,就不能及时开始下一帧的绘制。如果这个时间超过 16ms,当页面有动画效果需求时,动画因为浏览器不能及时绘制下一帧,这时动画就会出现卡顿。不仅如此,因为事件响应代码是在每一帧开始的时候执行,如果不能及时绘制下一帧,事件响应也会延迟。
时间切片(Time Slicing)
examble:曹冲称象
时间切片是一项使用得比较广的技术方案,它的本质就是将长任务分割为一个个执行时间很短的任务,然后再一个个地执行。
比如将 100000 个节点渲染到某个页面节点下,我们可以一次性循环100000 次生成节点,然后把节点挂载到页面节点上,但是会有明显的卡顿
let list = document.querySelector('.list')let total = 100000for (let i = 0; i < total; ++i) {let item = document.createElement('li')item.innerText = `我是${i}`list.appendChild(item)}
我们可以把 这100000 次循环 分成若干 个小任务,完成每个小任务后,浏览器就有时间更新下一帧的页面效果,从而解决卡顿问题
使用 requestAnimationFrame 和 createDocumentFragment api
let list = document.querySelector('.list')let total = 100000let onceNumber = 20let currentIndex = 0const renderToPage = (total, currentIndex) => {if(total <= 0){return}let loopNumber = Math.min(total, onceNumber);window.requestAnimationFrame(() => {let fragment = document.createDocumentFragment();for (let i = 1; i <= loopNumber; i++) {let item = document.createElement('li')item.innerText = `我是 new ${currentIndex + i}`fragment.appendChild(item)}list.appendChild(fragment)let newTotal = total - loopNumberlet newIndex = currentIndex + loopNumberif(newTotal > 0){renderToPage(newTotal, newIndex)}})}renderToPage(total, currentIndex)
链表 (Linked List)
React Fiber 中用链表遍历的方式替代了 React 16 之前的栈递归方案 它使用单链表树遍历算法。它使暂停遍历并阻止堆栈增长成为可能。