react源码学习-实现篇-优先级
Posted lin-fighting
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react源码学习-实现篇-优先级相关的知识,希望对你有一定的参考价值。
优先级
什么是优先级
生命周期方法:同步执行。
受控的用户输入:比如输入框内输入文字,同步执行。
交互事件:比如动画,高优先级执行。
其他:比如数据请求,低优先级执行。
如何调度优先级
Schedule提供了runWithPriority方法,
接受一个优先级常量和一个回调函数作为参数。回调函数
会以优先级
高低为顺序排列在一个定时器
中并在合适的时间触发。
Schedule对优先级的定义,值越低优先级越高。
下面可以通过一个demo来了解Schedule的流程。
[一文搞懂调度Schedule][https://blog.csdn.net/lin_fightin/article/details/122829688?spm=1001.2014.3001.5502]
例子
优先级最终会反映到update.lane变量上,当前我们只需要知道该变量能够区分update的优先级。
比如新产生了一个update为u1,他负责将黑色主题变为白色主题,他的优先级比较低。
u1先触发,进入render阶段,其优先级比较低,执行时间比较长。此时当前fiber的updateQueue为
fiber.updateQueue =
baseState:
blackTheme: 'true', //黑色主题
text: "H"
,
firstBaseUpdate: null,
lastBaseUpdate: null,
shard:
pending: u1
effects: null
在u1完成render阶段前,用户输入了I,想将H变为I,产生了新的update,称为u2,u2的优先级高于u1,所以在workLoopConcurrent函数执行的时候,shouleYield返回true的时候,表示当前桢无剩余时间,需要下一桢才能继续干活的时候,会继续调度,找到了刚才刚注册调度的关于u2的performConcurrentWorkOnRoot的注册函数被调度,优先级更高,所以中断之前u1的工作,开始执行u2的performConcurrentWorkOnRoot。
此时fiber对应
fiber.updateQueue.shared.pending === u2 ----> u1
^ |
|________|
// 即
u2.next === u1;
u1.next === u2;
在u2的render阶段,在processUpdateQueue方法中,shared.pending环状链表呗剪开拼接到了baseUpdate后面。所以此时update的执行顺序应该是u1 – u2
由于u2优先级更高,所以u1被跳过,又担心update之前可能有依赖关系,所以被跳过的update及后面的update会成为下次更新的baseUpdate,即u1 – u2
最终u2完成了render-commit阶段,先将H变为了I,此时fiber
fiber.updateQueue =
baseState:
blackTheme: true,
text: "I"
,
firstBaseUpdate: u1,
lastBaseUpdate: u2,
shared: pending: null,
effects: null
在commit阶段结尾,会在调度一次更新,在该次更新中,基于baseState中firstBaseUpdate保存的u1,开启了render阶段。最终两次update都完成的结果如下:
fiber.updateQueue =
baseState:
blackTheme: false,
text: 'HI'
,
firstBaseUpdate: null,
lastBaseUpdate: null
shared:
pending: null
,
effects: null
;
可以发现,u2对应的update在render阶段执行了两次,相应的render阶段的生命周期钩子也会触发两次,这也是为什么这些钩子会被标记unsafe_,因为可中断的异步更新可能导致某些钩子执行多次。
如何保证状态正确
1 render阶段可能被中断,如何保证updateQueue中保存的update不丢失。
2 当前状态可能依赖前一个状态,如何支持跳过低优先级的同时保证状态依赖的连续性。
1 保证update不丢失
在render阶段执行update的时候,也就是processUpdateQueue函数,hared.pending
的环被剪开并连接在updateQueue.lastBaseUpdate
后面。
实际上shared.pending
会被同时连接在workInProgress updateQueue.lastBaseUpdate
与current updateQueue.lastBaseUpdate
后面。
当render中断的时候,会基于current fiber的updateQueue克隆,所以不会丢失。
当高优先级的任务commit阶段完成渲染的时候,由于workInprogress updateQueue.lastBaseUpdate克隆了current updateQueue.lastBaeUpdate的值,保存了上一次的Update,所以当workInprogress fiber树 变为current fiber树
的时候也不会造成Update丢失。
2 保证状态的连续性
之前说过,当某个update因为优先级跳过的时候,该update和他之后所有的update会形成链表,存放在fiber.updateQueue的firstBaseUpdate上。
如有四个update, A1 B2 C1 D2 ,1的优先级比2高,所以A1, C1先执行,B2跳过。
baseState: ''
shared.pending : A1 => B2 => C1 => D2
第一次render的时候
baseState: ''
baseUpdate: null
render阶段使用的update [A1, C1]
memoizedState: "AC"
执行了A1和C1。第二次render的时候
baseState: 'A',
baseUpdate: B2 -> C1 -> D2
render阶段使用的update [B2, C1, D2]
memoizedState: "ABCD"
这里的baseState并不等于上一次更新的memoizedState,这是由于b2被跳过了,当有update跳过的时候,下次的baseState!=上次更新的memoizedState
在执行update的processUpdateQueue可以看到
对于执行优先级较高的update,当已经有update被跳过的时候,当前update需要clone一份保存。
至于baseState不等于上一次的memoizedState,逻辑在这里
从上可以看到,如果遇到了有需要跳过的update,不仅会保存他的update,还会保存当时queue.baseState作为下一次的newState。
当所有update执行完毕的时候,如果newLastBaseUpdate有值,那么就不会执行newBaseState = newState这个操作,那么queue.baseState = newBaseState永远是老的state。以上面的为例:
执行A1 ---> 不需要跳过,newLastBaseUpdate === null -> newBaseState = A(最新的state)
执行B2 ---> B2需要跳过,newLastBaseUpdate === B2 --> newBaseState = queue.baseState等于A.
执行C1 ---> C1不需要跳过,但B2跳过了,需要clone一份,newLastBaseUpdate === B2->C1 ---> newBaseState = queue.baseState还是A ---> queue.baseState = newBaseState = A1
执行D2 ---> D2需要跳过, newLastBaseUpdate === B2->C1->D2 ----> newBaseState ===queue.baseState还是A
do while循环结束后,newLastBaseUpdate有值,不将最新的state赋值,然后 queue.baseState = newBaseState 就等于A。
这就是为什么第二次render的时候baseState是A而不是AC
以上是关于react源码学习-实现篇-优先级的主要内容,如果未能解决你的问题,请参考以下文章