react源码学习-实习篇-状态更新
Posted lin-fighting
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react源码学习-实习篇-状态更新相关的知识,希望对你有一定的参考价值。
实现篇-状态更新
状态更新的流程-几个关键节点
先了解几个关键函数的调用,看看常见触发状态更新的方法是如何工作的。
render阶段的开始
之前就了解归,render阶段开始于performSyncWorkOnRoot/performConcurrentWorkOnRoot,他们会执行对应的performUnitOfWork方法。
commit阶段的开始
commit阶段开始于commitRoot方法的调用,rootFiber作为传参。
render阶段完成后会进入commit阶段。那么从触发状态更新,到render阶段的路径又经过了什么呢?
触发状态更新(this.setState) ------>. ? ------> render阶段 ----> commit阶段
1 创建update对象
在react中,有以下方法触发状态更新:
ReactDOM.render
this.setState
this.forceUpdate
useState
useReducer
这些方法调用的场景各不相同,如何接入同一套状态更新机制呢?
那就是每次状态更新都会创建一个保存更新状态内容的对象,Update,然后在render阶段的beginWork中根据Update对象计算最新的state。
每次状态更新都会创建对应的update对象。(后面详细讲,现在先知道会创建一个update对象),然后存放在fiber上。
2 从有update对象的fiber到root
render阶段是重rootFiber开始向下遍历的,如何根据当前有update对象的fiber找到rootFiber呢?
从触发状态更新的fiber
一直向上遍历到rootFiber
,并返回rootFiber
。由于不同更新优先级不尽相同,所以过程中还会更新遍历到的fiber
的优先级,现在暂不了解。
3 调度更新
现在已经有了一个rootFiber了,该rooFiber所在的fiber树上有某一个fiber节点包含一个update对象。那就需要通知Scheduler根据更新的优先级,决定以同步或者异步调度本次更新。
就是ensureRootIsScheduled方法,看在他的核心代码:
根据任务的优先级,决定同步还是异步调度,shceduleCallback和s cheduleSyncCallback会调用Schedule提供的带哦度方法根据优先级调度其回调函数执行
可以看到这调度的回调函数是
performSyncWorkOnRoot.bind(null, root)
performConcurrentWorkOnRoot.bind(null, root)
这两个就是render阶段的入口,至此,状态更新与render阶段连接上了。
触发状态更新(this.setState) ------>创建update对象 ---> 从fiber到root(markUpdateLaneFromFiberToRoot) ---> 调度更新(ensureRootIsScheduled)
------> render阶段 ----> commit阶段
以this.setState为例子:
调用this.setState会调用enqueueSetState方法,创建update对象(这里是calssComponent,对应的update的payload就是第一个参数),赋值callback,然后将update插入fiber.updateQueue上,接着执行scheduleUpdateOnFiber,该方法刚才也看见了
找到rootFiber,并且调度更新,执行performSyncWorkOnRoot/performConcurrentWorkOnRoot,开始render阶段。
创建Update
update对象是构成 react concurrent mode的关键。
每次状态更新,都会创建对应的Update,react中一共有三种组件,HostRoot(rootFiber), ClassComponent,FunctionComponent可以出发更新,不同组件工作方式不同,所以Update分为两种结构,其中,HostRoot和ClassComponent共用一种,FunctionComponent单独使用一种Update结构,虽然结构不同,但是他们的作用机制与工作流程大体相同。
Update的结构
类组件的update
字段的意义:
-
eventTime: 任务时间
-
lane 优先级相关字段,不同update的优先级不同。
-
tag 更新的类型,包括 UpdateState
|
ReplaceState|
ForceUpdate|
CaptureUpdate -
payload 更新挂在的数据,不同类型组件挂在的数据不同,对于类组件,payload就是this.setState的第一个传参。对于HostRoot,payload为reactDom.render的第一个传惨。
-
Callback: 更新的回调函数 //setState的第二个参数,在commit阶段的layout阶段执行, 对于
HostRoot
,即rootFiber
,如果赋值了第三个参数回调函数
,也会在此时调用 -
next 与其他update连接形成链表,放在fiber.updateQueue.shared.pending上。
类似于fiber树,update以next指针组成一个链表,一个fiber节点上的多个update以链表的形式存放在fiber.updateQueue.shared.pending上。
每个fiber节点最多同时存在两个updateQueue --Fiber.updateQueue,和fiber.alternamte.updateQueue。
即current fiber保存的updateQueue和workInProgress fiber上保存的updateQueue。
在commit阶段
完成页面渲染后,workInProgress Fiber树
变为current Fiber树
,workInProgress Fiber树
内Fiber节点
的updateQueue
就变成current updateQueue
。
updateQueue
三种类型,对于HostComponent,在completeWork的时候我们知道他会处理props存放在fiber.updateQueue上。而剩余的两种类型,刚好与两种update类型对应。
对于类组件使用的updateQueue:
-
baseState 保存本次更新前fiber的state。update会基于该state计算更新后的state。
-
firstBaseUpdate, lastBaeUpdate: 本次更新前如果该fiber有已保存的update,那么他们会以链表的形式存在,表头为firstBaseUpdate,表尾为lastBaeUpdate。
-
Shared.pending,存放着update组成的环状链表。比如ABCD,那么顺序将会是: shared.pending = D => A =>B => C => D。计算的时候,该环状链表会剪开,然后连接在lastBaeUpdate后面,如 lastBaseUpdate => A => B => C => D
-
Effets:保存update.callback不为null的update。即有回调函数的update。
更新的时候,会遍历该update链表,依此执行每个update计算产出新的state(类似于Array.prototype.reduce),获取到的新的state,就是本次更新的state,源码中叫做memoizedState。
在render阶段beginWork的时候,update的操作由processUpdateQueue完成,
function beginWork(...)
...
// mount时:根据tag不同,创建不同的子Fiber节点 update时,处理对应fiber上的updateQueue的update对象
switch (workInProgress.tag)
...
case ClassComponent:
...
return updateClassComponent( //处理类组件 调用updateClassInstance,updateClassInstance调用processUpdateQueue处理类组件的updateQueue
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
...
return updateHostRoot(current, workInProgress, renderLanes); // 调用processUpdateQueue
...
case CacheComponent:
if (enableCache)
return updateCacheComponent(current, workInProgress, renderLanes); // 调用processUpdateQueue
break;
....
如图,他会将环状链表拆开,然后接入到lastBaseUpdate后面。然后
do while遍历执行update,优先级较低的,会放到新的neFirstBaseUpdate上去。
优先级较高的,执行该update,获取最新的state
最后一些收尾工作,更新queue上的fi rstBaseUpdate和baseState,更新当前fiber的memoizedState。
processUpdateQueue负责执行当前fiber的updaeQueue,计算出新的state保存。
state的变化在render阶段产生于上次不同的jsx对象,通过diff算法产生effectTag,赋值到新的fiber上,在commit阶段渲染到页面上。
总结:
-
状态更新会产生update,update分为两种,函数组件一种,类组件和HostComponent一种。多个update以next相连,形成环状链表,存放在fiber.updateQueue.shared.pending上。
-
而updateQueue又有三种类型,其中一种为HostComponent处理props生成的payload。第二种对应类组件的update,它上面存放着对应的update链表,还有当前的update使用的state。
-
他们的关系就是: fiber上有updateQueue,updateQueue.shared.pending上存放着update组成的链表。
-
创建完update之后,就会根据fiber找到rootFiber,然后开始调度,到达render阶段,render阶段会根据tag的不同,处理对应的fiber上的updateQueue,比如类组件上的updateQueue和hostRoot的updateQueue,他会计算出最新的state,赋值给fiber的memoizedState上。然后会产生不同的jsx对象,根据diff算法产生effecTag,挂到fiber上,在commie阶段渲染到页面。
-
更新流程: 以this.setState为例子,调用this.setState会调用enqueueSetState方法,创建update对象,赋值callback,然后将update插入fiber.updateQueue上,接着执行scheduleUpdateOnFiber, 找到rootFiber,并且调度更新,执行performSyncWorkOnRoot/performConcurrentWorkOnRoot,开始render阶段(处理update产生新的fiber),开始comit阶段,结束。
触发状态更新(this.setState) ------>创建update对象 ---> 从fiber到root(markUpdateLaneFromFiberToRoot) ---> 调度更新(ensureRootIsScheduled) ------> render阶段(处理updateQueue) ----> commit阶段
首次渲染流程:以ReactDOM.render为例子
首次执行ReactDOM.render会创建fiberRootNode和rootFiber
调用legacyRenderSubtreeIntoContainer,然后
legacyCreateRootFromDomContainer创建rootFiber和FiberRootNode,最终会调用
可以看到rootFiber 和fiberRootNode的联系就是 fiberRootNode.current = rootFiber; rootFiber.stateNode = fiberRootNode。
然后初始化rootfiber的updateQueue
现在已经做好了组件的初始化工作,只需要等待来一个update开启第一次更新。
接着执行updateContainer,这个函数很重要,是hostComponent创建update的方法。
可以看到,熟悉的流程,创建updat对象,hostComponent的update的pa yload对应的是render的第一个传惨,然后直接执行scheduleUpdateOnFiber方法,找到root fiber,调度执行开启render阶段。
所以第一次mount的时候,执行ReactDOM.render也可以看作是一次更新。
执行ReactDOM.render
----> 创建rootFiber和fiberRootNode,初始化rootFiber的updateQueue (legacyCreateRootFromDOMContainer)
----> 创建第一次渲染的update (UpdateContainer)
----> 开启调度(scheduleUpdateOnFiber) =》根据fiber找到rootFiber(markUpdateLaneFromFiberToRoot) => 调度更新 (ensureRootIsScheduled)
----> 判断同步异步,执行performSyncUnitOfWork/performConcurrentWorkOnRoot 开启render阶段
----> render阶段处理HostComponent的updateQueue(beginWork中根据各组件调用processUpdateQueue处理updateQueue产生effectTag(mount只有rootFier会 产生effectTag))
----> commit阶段渲染到页面 (before-mutation -> mutation -> layout)
渲染流程完成。
以上是关于react源码学习-实习篇-状态更新的主要内容,如果未能解决你的问题,请参考以下文章