You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
exportconstFunctionComponent=0;// FC对应的Fiber节点exportconstClassComponent=1;exportconstIndeterminateComponent=2;// Before we know whether it is function or classexportconstHostRoot=3;// 根FiberexportconstHostPortal=4;// A subtree. Could be an entry point to a different renderer.exportconstHostComponent=5;// DOM文档的节点对应Fiber 如div,section...exportconstHostText=6;// 文本节点exportconstFragment=7;exportconstMode=8;exportconstContextConsumer=9;exportconstContextProvider=10;exportconstForwardRef=11;exportconstProfiler=12;exportconstSuspenseComponent=13;exportconstMemoComponent=14;exportconstSimpleMemoComponent=15;exportconstLazyComponent=16;exportconstIncompleteClassComponent=17;exportconstDehydratedSuspenseComponent=18;exportconstEventComponent=19;exportconstEventTarget=20;exportconstSuspenseListComponent=21;
谈谈React Fiber与分片
React的理念和Fiber的出现
从React的Doc上可以看到React的理念是:
但是我们有时候一个很长很深的DOM列表(在没有做列表优化的前提下),
setState
创建更新后,React会进行对比创建前和创建后的节点(Reconcilation
阶段),对比的过程是不可中断的,由于网页的主线程不仅包含了
js
执行,样式计算
, 还包含了渲染需要的重排重绘
,也就是当Reconcilation
(js执行任务)执行很久的时候,当前的任务在主线程占用时间过多,就会影响浏览器正常的重排
/重绘
,也会影响正常的用户交互(输入,点击,选择等等)。举个比较极端的例子,我们有个很深的列表(1500层),而且变化频繁:
从

performance
面板看,也是changeRandom
所触发的整个js执行任务
占用了161ms
从事件循环看,

changeRandom
函数执行setState
进入reconcilation
阶段,但是由于列表层次太深,整个过程又是不可中断的,所以耗时多阻碍了其他任务包括键盘输出
,样式计算
,重排
,重绘
等:从浏览器的一帧来看,当上述的

reconcilation
task阻塞了太久,导致正常刷新率情况下的每帧16.6ms下没有更新视图,造成掉帧的问题所以基于
React
的理念,为了解决上述的问题,实现reconcilation
过程可中断,当然包括其他比如Concurrent Mode
的那些试验性的feature, 以及优先级调度相关的东西, React决定使用Fiber
重写底层实现Fiber的数据结构和Fiber树的构建
还是上述的代码,我们随便找一个渲染出来的DOM元素
即可看到当前节点对应的Fiber信息

列出主要的几个Fiber数据结构:
demo中构建的Fiber树是这样的:

React渲染的两个流程
interruptible
)beginWork
主要的作用是创建初始化和更新的当前Fiber节点的子Fiber节点,并且返回当前Fiber的第一个子节点去开始下一次performUnitOfWorkcompleteWork
主要是创建Fiber.stateNode的过程,即根据beginWork
生成的新Fiber调用document.createElement
去创建DOM节点存储在Fiber.stateNode中,再在commit
流程的时候去append
到真实的DOM中Scheduler: 调度模块,调度
render/reconcilation
阶段的任务,将任务分为5ms一个,可中断开启Concurrent Mode以及分片
启动Fiber时间分片功能需要开启Concurrent Mode模式,也就是说我们平时开发中默认用的
ReactDOM.render
,虽然用了Fiber,但其实没有用到时间分片。开启Concurrent Mode只需要两个步骤:
ReactDOM.createRoot
创建一个FiberRoot
再render
替代ReactDOM.render
开启完毕。
当然除了我们平常用的

ReactDOM.render
的Legacy Mode
以及Concurrent Mode
, React还出了一个Blocking Mode
,其实就是拥有部分Concurrent Mode
功能的一个中间版本,创建方式是:ReactDOM.createBlockingRoot(rootNode).render(<App />)
三种模式的对比:
可以看到在ConcurrentMode下,开始有了SuspenseList,可以控制Suspense组件的一个顺序,也支持了优先级渲染,中断预渲染等,还有一些新的
hook
, 比如用useTransition
搭配Suspense
可以用来做加载的优化,useDefferredValue
来做一些state
值的缓存,对于某些优先级不是很高但是又很耗时间的更新,可以不用立即更新,而是获取deffer
延迟的state等等,但是这些还是还是在试用包里面,可能随时会改,所以就不细说,感兴趣的可以看下:Suspense for Data Fetching
开启分片后性能和用户体验对比(concurrent mode vs legacy mode)
我们回到最开始的demo, 我们对比下开启


Concurrent Mode
(也就是开启分片)前后的performance
面板对比:开启分片前:
可以看到主线程上的每次更新都是由
changeRandom
发起然后再进行reconcilation
阶段和commit
阶段,整个方法包含在一个Task
里面:开启分片后:

开启分片后,可以看到
render/reconcilation
阶段分成了很多个任务,有很多个Task
都是5ms
的任务这时候我们加一个输入框,来测试是否用户体验提升很多,是否
reconcilation
分片真的这么强。于是加个输入框,并且不要让输入框影响随机的
div
深列表,所以我们把List
单独抽出来,并且用React.memo
包起来,这样输入框的setState
并不会引发List
的重渲染:外层:
随机List:
在


Legacy
模式下,可以看到输入框的输入有点卡顿,主要是整个render/reconcilation
任务占用了太多时间导致然后我们开启
Concurrent Mode
,发现还是被阻塞了,还是会有一点卡,看performance
发现主要是被commit
和layout/paint
两个流程卡住了,当然当输入也没有刚好卡在

render/reconcilation
的分片当中,是不会被render/reconcilation
本身阻塞的,所以可以总结:render/reconcilation
的分片以及达到了效果,在分片的间隔时间已经可以去插入执行其他优先级更高的用户相应了。但是,为了更好的演示分片带来的效果,我决定排除
commit
流程和Layout/Paint
重排重绘带来的影响。抛开Layout/Paint流程和commit流程来看分片带来的performance优化
style={{ display: 'none' }}
, 这样render/reconcilation
阶段完成之后就不会进行重排重绘了,但是生成的Fiber
还是会生成,只不过最后commit到DOM上也不会渲染为了效果更明显,我直接拷贝多了两个List, 并且每个List增加到3000条,
这样, 在

Legacy
模式下, 我们看到performance里面,已经没有Layout/Paint
这样的任务来阻碍我们的输入了,只剩下commit
,现在加大List数量的情况下,render/reconcilation
大概阻塞了500多ms, 很卡。这时候再看

Concurrent Mode
,很流畅,commitRoot直接没有了(也算是Concurrent Mode一个优化吧,对于display:none
来说,本身commit就没有必要)总结及Concurrent Mode的其他Features
当然我们举了很夸张的例子(深节点,移除重排重绘)来单独看
Concurrent Mode
模式下对于render/reconcilation
带来的优化效果。当然分片只是Fiber的一小部分功能,Fiber
架构解锁了很多Concurrent Mode
的新功能:<SuspenseList>
,useTransition
,useDeferredValue
等等,当然这些暂时是试用性的。 在我们的demo中,可以使用useDeferredValue
来做state的延迟,比如我们轮训获取到了实时的长列表,但是又不想阻塞输入框等用户的操作,我们可以将旧的state
用useDeferredValue
暂时存起来,然后将旧版的state传给带memo的组件,这时候我们通过降低了列表的时效性来换取了用户交互体验的提升,而且我们原state永远是最新的,所以跟增大轮询时间又不太一样。总之,
Concurrent Mode
解锁了很多新的功能,当然有些是试用性的,但是可以期待当Concurrent Mode
正式使用的时候,新特性给性能和用户体验带来的提升。The text was updated successfully, but these errors were encountered: