首先感谢司徒正美大佬写的文章,让我大致了解了fiber是个什么东西。(愿天堂没有996 T_T)这里推荐两篇大佬的文章React Fiber架构,React Fiber的优先级调度机制与事件系统,第一篇讲了fiber行为,第二篇讲解了fiber事件分片的机制
先康一康react官方对于concurrent的介绍,其实就是当页面元素过多的时候,每次更改涉及到的元素过多时,由于原算法是深度遍历v-dom去修改状态,当遍历花费时间过多时会造成页面卡顿。而fiber架构允许遍历被打断去响应高优先级操作,让页面更加流畅。具体demo可以从以下例子中看到。https://claudiopro.github.io/react-fiber-vs-stack-demo/。
为了让每一个节点都有能力去遍历整棵树,fiber设计了三个属性return child sibling,三者分别对应该节点的父节点、子节点、右侧兄弟节点。这样的话我们每次拿到一个节点都有能力去获得发生变化的根节点、访问到未被更新过的节点(如果是树状结构,每一个节点就只能访问到其子节点了)。fiber结构可以满足先前的所有特性,同时还可以衍生出current mode的特性,所以现在react的v-dom已经是fiber结构了(react 16开始)
这里首先明确一个点,fiber并不能提高代码执行效率,相反,fiber降低了执行效率,但是由于某些高优先级事件会优先执行,所以它只是优化了用户体验,真正从diff开始到渲染结束花的时间时更长的。具体可以参考上述React Fiber架构文章内的例子,分批次渲染页面可以让页面提前显示出来(常说的首屏响应时间)。
具体如何在diff过程中停止呢,react设计了多个优先级,优先级有对应值,这个对应值就是这个fiber的更新延迟时间,最快的值为-1,就是立即更新,最慢的10000,最佳计时api其实是,requestIdleCallback但是这个api兼容性不行,而且它是个浏览器api,react要支持server render是不能用这个api的,所以react源码定义了一个requestHostTimeout。本质上是requestIdleCallback的polyfill,兼容node和不同浏览器用(其实本质还是用的setTimeout和requestAnimationFrame,现在已经不用requestAnimationFrame了使用的messageChannel,具体原因可以看以下commit),似乎是考虑到硬件刷新率不同而做的更改。
1 | // 优先级定义如下 |
现在如果不考虑如何将需要更新fiber节点添加进入更新队列,而只是单纯的看调度方法的话,主要有如下代码
1 |
|
这里的主要入口时unstable_scheduleCallback,跑到react-dom里面找这个代码的相关引用,可以直接找到一下调用链commitWork -> commitHydratedContainer -> retryIfBlockedOn -> scheduleCallbackIfUnblocked -> unstable_scheduleCallback。这里就很清晰这个unstable_scheduleCallback就是最终commitWork的实现。
这里就很容易就能看懂逻辑了!在commitWork之后,执行requestHostCallback,requestHostCallback执行postMessage,然后MessageChannel接收到消息之后继续执行requestHostCallback,一直持续到没有work为止,至于什么时候没有work呢,就是任务队列里面没有任务,或者时间到deadline了,具体是通过requestHostCallback来判断,我们通过这个函数去找,可以找到最终return 的是workLoop函数
1 |
|
看见了终于看见了,currentTask.expirationTime > currentTime && (!hasTimeRemaining || exports.unstable_shouldYield())这个条件,判断是否时间结束或者是否应该暂停(暂停update也是concurrent mode的一个特性嗷),那具体这个task.expriationTime计算呢,是在unstable_scheduleCallback中,值就是当前时间+优先级的值。
到此为止,concurrent mode的调度,大致就是这样了,具体关于如何去遍历fiber,什么时候去commitWork,阔以自己看看源码(因为我还没去看这块代码,XD)