[React Origin Code] React Update [1.6k 字 - 阅读时长3min]

5,278 阅读5分钟

React Update-- 双缓存Fiber树

学习源码不易,和大家一起共勉!

前文: [React Origin Code] 2022年来聊聊React Mount-- renderRootSync当中,我们刚刚说完 React Mount,今天我们继续来聊聊React update,在聊update之前,我们还需要再次回到mount阶段,这次主要把握和关注两个概念,workInProgress树current树的双缓冲Fiber机制。这在React update阶段起到了至关重要的作用。

关于双缓存树的资料

双缓存

React 双缓存Fiber树

下面我们以React 计数器案例来聊聊React update逻辑

image.png

图一:计数器案例来说明React update逻辑

createWorkInProgress

下面这段总结读完本文,可以回过头来看看:

createWorkInProgress函数mount的时候只会走一次,就是创建workInProgress树的根节点,mount的其他节点不进到createWorkInProgress函数,因为此时没有currentfiber树,其他节点的current === null,当执行到reconcileChildren函数的时候,走的是mountChildFibers,只有更新的时候,current !== null ,走reconcileChildrenFibers的时候,才让每个子节点先去递归执行createWorkProgress,复用建立workInProgressfiber节点,建立好workInProgress树current树,并且用alternate属性,对两棵树的节点进行联系。

image-20221008153300672.png

图二:不同的reconcilChildren逻辑

beginwork前的fiber树

关于mount阶段, beginwork函数的作用和流程,我们在前文已经详细聊过了, 在bedginwork之前,会调用createWorkInProgress函数

mount的时候,在刚进入createWorkInProgreess的函数上打上断点,打印fiber树。结果是这样的。

image-20221009084003763.png

图三:monunt 刚进入createWorkInProgreess的函数的打印结果

6dbd2c1abd78c494b3a785a794706ac.jpg

图四:mount 刚进入createWorkInProgreess的currentFiber树

mount的时候,执行的第一次也是最后一次的createWorkInProgreess函数结果是这样的

1ef6f5afa126949a8e1a3ef2bdd166a.jpg

图五:mount的时候,执行的第一次也是最后一次的createWorkInProgreess后的Fiber树

然后从workProgreess树的根fiber节点开始 mount,关于mount的流程这篇文章很详细,mount之后结果是这样的。

dd37ff8ee17590866bea8abfcf5a6a1.jpg

图六:mount fiber树之后结果

细节注意:下一次更新之前,刚刚 mount时的workInProgress树会被变成current树,这里的变,也仅仅时需要改变FiberRootNode.current指针而已。这也是双缓存机制的魅力所在。

第一次触发计数器 + 1 -> update

mount结束后,我们触发计数器+1, 开始第一次更新,我们由上图可以知道,mount之后,第一次更新之前只有根节点有自己的alternate,所以在createWorkInProgress函数时,根节点fiber可以复用current树的 fiber,由于复用current fiber之后其child也被复用,所以结果是这样的。

7625872d6cfb424e85f9925571cc107.jpg

我们发现第一次更新,除了根节点FiberRootNode之外每次先递归来到createWorkInProgress来执行,curretn.alternatte都是null,所以每次都回去createFiber去创建新的workProgress节点,第一次更新之后就会在每次createWorkInProgress的时候,当前workInprogressfiber就会和 current fiber建立alternative相互链接。然后去递归beginwork,reconcileChildren,然后再让children fiber去作为根节点去createWorkInProgress。begin这里有一个更新逻辑,每次我们来到带着cr创建好的workj fiber节点和current的时候,会有一个优化,didReceiveUpdate 如果为true会走优化逻辑。通过新旧props 对比,新旧type对比,以及是否有context的改变来对比,bailoutOnAlreadyFinishWork逻辑,会执行cloneChildFibers 返回子fiber。如果没有命中,继续根据type来updatae不同类型的标签组件。

image-20221009090344140.png

update阶段 beginwork 和 createWorkInProgress函数调用栈的关系 reconcile也进入了不一样的逻辑,这一次current fiber节点不在是null了,会进入reconcileChildrenFibers而不是 mountChildrenFibers这时shouldTraceEffect 会变为true给一些需要新插入的节点打上effectTag Placement, 或者是删除节点打上 delection

beginwork后创建建立双缓存Fiber树的结果是这样的:

eb2a30f9255659c04e768dab95b3a2e.jpg

completework

completework老朋友了,workInProgress树回溯阶段,从下至上,对props 进行diff来 删除或者来收集变化属性到fiber节点的updateQueue当中,第i属性为变化的属性,i+ 1属性是属性变化的值,将需要更新的节点打上effectTag, update,收集到effectList的 链表当中,complete 回溯到根节点的时候,从根节点出发的 firstEffect属性 就指向 第一个需要comit 变化的fiber,next下一个,一直到lastEffect最后,最后commitWork 根节点。commit阶段更新渲染视图.这样做的好处是不用再像renderer阶段一样深度优先遍历,都从根节点出发去遍历到每一个子节点去看看fiber节点是否发生变化,来打上相对应的effecttag。

第二次触发计数器 + 1 -> update

eb2a30f9255659c04e768dab95b3a2e.jpg

第二次更新前的Fiber树

第二次更新很关键,因为这时内存当中有两颗react fiber树 一棵树是w,一棵树是 c,而且之前建立了alternate联系,这一次建立新fiber的时候 除了新添加的节点,其他节点都在第一次更新的基础上增加了altenate属性,所以这一次每次不用在重新创建,workINprogrss fiber节点了,我们复用wok 的 al c来 属性来获取fiber节点。这一次和以往不同的地方还有,

image-20221009094100380

image-20221009101521420