优先级相关
- NoPrioritiy: 初始化时无优先级
- ImmediatePrioritiy: 立刻执行的优先级 (也就是同步执行的优先级)
- UserBlockingPrioritiy: 用户触发的更新优先级,如点击事件等等的优先级
- NormalPrioritiy:一般的更新级,请求数据返回时更新状态
- LowPrioritiy:Suspence使用的
- IdlePrioritiy: 空闲时的优先级
状态更新
每次状态更新都会创建一个保存更新状态相关内容的Update对象。在
render阶段
的beginWork方法
中会根据Update计算新的state。- 触发状态更新(根据场景调用不同方法)
- 创建Update对象
- ReactDOM.render
- this.setState
- this.forceUpdate
- useState
- useReducer
- 从fiber到root(
markUpdateLaneFromFiberToRoot
)
- 调度更新(
ensureRootIsScheduled
):首先如果rootFiber
对应的Fiber树
中某个Fiber节点
包含一个Update,然后通知Scheduler
根据更新的优先级,决定以同步
还是异步
的方式调度本次更新
- render阶段(
performSyncWorkOnRoot
或performConcurrentWorkOnRoot
)
- commit阶段(
commitRoot方法
,传入rootFiber)
Update结构
ClassComponent 和 HostRoot 共用一套Update结构
Update由createUpdate方法返回,可以从这里看到createUpdate的源码
Fiber节点上的多个Update会组成链表并被包含在
fiber.updateQueue
中。什么情况下一个Fiber节点会存在多个Update?如下就是一种:
onClick() { this.setState({ a: 1 }) this.setState({ b: 2 })}
在一个
ClassComponent
中触发onClick
方法,方法内部调用了两次this.setState
。这会在该Fiber节点
中产生两个Update
。const update: Update<*> = { eventTime,//发起update事件的时间,performance.now()获取毫秒时间(17.0.2中作为临时字段, 即将移出) lane,// update所属的优先级 tag: UpdateState,//更新的类型 UpdateState/ReplaceState /ForceUpdate/CaptureUpdate payload: null, // 更新挂载的数据,不同类型组件挂载的数据不同。HostRoot为需要挂载在根节点的组件,也就是ReactDom.render的第一个参数,而ClassComponent为this.setState的第一个参数 callback: null,//更新的回调函数,commit完成之后会调用。也就是ClassComponent中this.setState的回调函数,也就是第二个参数、HostRoot中render的第三个参数 next: null,//指向下一个update对象,形成链表。如高优打断低优的任务,或者同一个事件中多次触发setState会创建多个Update对象依次连接。由于UpdateQueue是一个环形链表, 最后一个update.next指向第一个update对象.};const queue: UpdateQueue<State> = { baseState: fiber.memoizedState, firstBaseUpdate: null, lastBaseUpdate: null, shared: { pending: null, }, effects: null, }; fiber.updateQueue = queue;//=========UpdateQueue==========type SharedQueue<State> = {| pending: Update<State> | null,//指向即将输入的update队列. 在class组件中调用setState()之后, 会将新的 update 对象添加到这个队列中来.|};export type UpdateQueue<State> = {| baseState: State, firstBaseUpdate: Update<State> | null, lastBaseUpdate: Update<State> | null, shared: SharedQueue<State>, effects: Array<Update<State>> | null,//用于保存有callback回调函数的 update 对象, 在commit之后, 会依次调用这里的回调函数.|};
UpdateQueue由
initializeUpdateQueue
方法返回,initializeUpdateQueue的源码- baseState:本次更新前该Fiber节点的state,Update基于该state计算更新后的state。
- firstBaseUpdate与lastBaseUpdate:本次更新前该Fiber节点已保存的Update。以链表形式存在,链表头为firstBaseUpdate,链表尾为lastBaseUpdate。之所以在更新产生前该Fiber节点内就存在Update,是由于某些Update优先级较低所以在上次render阶段由Update计算state时被跳过。
- shared.pending:触发更新时,
新产生的Update
会保存在shared.pending
中形成单向环状链表
。shared.pending
会保证始终指向最后一个插入的Update。当由Update计算state时这个环会被剪开并连接在lastBaseUpdate后面。
- effects:数组。保存update.callback !== null的Update。
这里的updateQueue有三种类型,
- HostComponent 的UpdateQueue中是数组,i项存储key i+1项存储value (在completeWork这部分有使用过)
- ClassComponent和HostRoot中的UpdateQueue
- FunctionComponent中的UpdateQueue
FunctionComponent单独使用一种Update结构
//除memoizedState以外字段的意义ClassComponent的 updateQueue类似export type Hook = {| memoizedState: any, baseState: any, baseQueue: Update<any, any> | null, queue: UpdateQueue<any, any> | null, next: Hook | null,|};type Update<S, A> = {| lane: Lane, action: A, eagerReducer: ((S, A) => S) | null, eagerState: S | null, next: Update<S, A>, priority?: ReactPriorityLevel,|};type UpdateQueue<S, A> = {| pending: Update<S, A> | null, dispatch: (A => mixed) | null, lastRenderedReducer: ((S, A) => S) | null, lastRenderedState: S | null,|};
hook与FunctionComponent fiber都存在memoizedState属性,
- FunctionComponent中的UpdateQueue
updateQueue工作流
假设有一个Fiber节点刚刚完成commit阶段的渲染,这个Fiber节点上存在两个由于优先级过低并没有被更新的Update(update1,update2),就会成为下次更新的baseUpdate,也就是render阶段跳过了这两个update处理。
fiber.updateQueue.firstBaseUpdate = update1;fiber.updateQueue.lastBaseUpdate = update2;update1.next = update2;
链表指向:fiber.updateQueue.baseUpdate: update1->update2
然后在Fiber上触发两次状态更新,这会先后产生两个新的Update(update3,update4)
enqueueUpdate
就是为Fiber节点增加新的Update的方法
首先获取到
shared.pending
,如果存在update,就把新的update.next
指向pending.next
,pending.next
指向新的update
,形成环状链表const pending = sharedQueue.pending; if (pending === null) { // This is the first update. Create a circular list. update.next = update; } else { update.next = pending.next; pending.next = update; } sharedQueue.pending = update;
每个 Update 都会通过 enqueueUpdate 方法插入到 Fiber的updateQueue.shared.pending上
fiber.updateQueue.shared.pending === update3;update3.next === update3;
也就是shared.pending是保存了update3的环形链表
插入update4后:
fiber.updateQueue.shared.pending === update4;update4.next = update3;update3.next = update4
环形链表指向 shared.pending:update4->update3->update4
假如还有update5:
fiber.updateQueue.shared.pending === update5;update5.next = update3update4.next = update5;
环形链表指向 shared.pending:update5->update3->update4->update5
shared.pending 会保证始终指向最后一个插入的update
更新调度完成后进入render阶段,这个时候
shared.pending
的环被剪开并连接在updateQueue.lastBaseUpdate
后面:processUpdateQueue
export function processUpdateQueue<State>( workInProgress: Fiber, props: any, instance: any, renderLanes: Lanes,): void { // This is always non-null on a ClassComponent or HostRoot const queue: UpdateQueue<State> = (workInProgress.updateQueue: any); let firstBaseUpdate = queue.firstBaseUpdate; let lastBaseUpdate = queue.lastBaseUpdate; // Check if there are pending updates. If so, transfer them to the base queue. let pendingQueue = queue.shared.pending; if (pendingQueue !== null) { queue.shared.pending = null; // The pending queue is circular. Disconnect the pointer between first // and last so that it's non-circular. const lastPendingUpdate = pendingQueue; const firstPendingUpdate = lastPendingUpdate.next; lastPendingUpdate.next = null; // Append pending updates to base queue if (lastBaseUpdate === null) { firstBaseUpdate = firstPendingUpdate; } else { lastBaseUpdate.next = firstPendingUpdate; } lastBaseUpdate = lastPendingUpdate; }}
render阶段的这个方法就是根据
queue.shared.pending!==null
来判断 是否有新的更新,如果有的话,会把他们转入baseQueue:fiber.updateQueue.baseUpdate:update1->update2->update3->update4
合并入updateQueue
接下来遍历
updateQueue.baseUpdate
链表,以fiber.updateQueue.baseState
为初始state,依次与遍历到的每个Update计算并产生新的state。在遍历时如果有优先级低的Update会被跳过。
当遍历完成后获得的state会保存在Fiber节点的memoizedState属性上,就是该Fiber节点在本次更新的state
比如说updateCount设置的值依赖于上一次count的值,React怎么能够获取上次的值呢?
const onClick = () =>{ updateCount(count => count + 1)}
Update值不丢失
- current Fiber保存的updateQueue即current updateQueue
- workInProgress Fiber保存的updateQueue即workInProgress updateQueue
在
commit阶段
完成页面渲染后,workInProgress Fiber树
变为current Fiber树
,workInProgress Fiber树
内Fiber节点
的updateQueue
就变成current updateQueue
。- 在
render阶段
,fiber.updateQueue.shared.pending
这个环状链表被剪开并且连接到updateQueue.lastBaseUpdate后面
- 当
render阶段
被中断后重新开始时,会基于current updateQueue
克隆出workInProgress updateQueue
。由于current updateQueue.lastBaseUpdate
已经保存了上一次的Update,所以不会丢失。
- 当
commit阶段
完成渲染,由于workInProgress updateQueue.lastBaseUpdate
中保存了上一次的Update,所以workInProgress Fiber树
变成current Fiber树
后也不会造成Update丢失。
当某个Update由于优先级低而被跳过时,保存在baseUpdate中的不仅是该Update,还包括链表中该Update之后的所有Update。
ReactDOM.render
ReactDOM.render调用栈
创建Fiber
let root: RootType = (container._reactRootContainer: any);let fiberRoot;if (!root) { // Initial mount //container是挂载的节点,ReactDOM.render的第二个参数 root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, ); fiberRoot = root._internalRoot;//创建FiberRoot .....
其中
legacyCreateRootFromDOMContainer方法
最终调用的createRootImpl
方法,其中createContainer方法调用了createFiberRoot方法
,这里面真正创建了fiberRootNode
,rootFiber
以及进行两者的关联
,最后初始化了updateQueue
:export function createFiberRoot( containerInfo: any, tag: RootTag, hydrate: boolean, hydrationCallbacks: null | SuspenseHydrationCallbacks,): FiberRoot { const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any); if (enableSuspenseCallback) { root.hydrationCallbacks = hydrationCallbacks; } // Cyclic construction. This cheats the type system right now because // stateNode is any. const uninitializedFiber = createHostRootFiber(tag); root.current = uninitializedFiber; uninitializedFiber.stateNode = root; initializeUpdateQueue(uninitializedFiber); return root;}
创建update
做好了组件的初始化工作后,接下来就等待创建Update来开启一次更新。updateContainer
export function updateContainer( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component<any, any>, callback: ?Function,): Lane { // ... // 创建update const update = createUpdate(eventTime, lane, suspenseConfig); // update.payload为需要挂载在根节点的组件,也就是说对于HostRoot,payload为ReactDOM.render的第一个传参 update.payload = {element}; // callback为ReactDOM.render的第三个参数 —— 回调函数 callback = callback === undefined ? null : callback; if (callback !== null) { update.callback = callback; } // 将生成的update加入updateQueue enqueueUpdate(current, update); // 调度更新 scheduleUpdateOnFiber(current, lane, eventTime); // ...}
流程如下:
- 创建FiberRootNode、rootFiber、初始化updateQueue(legacyCreateRootFromDOMContainer==>createRootImpl==>createFiberRoot)
- 创建Update对象(updateContainer),放入fiber节点的updateQueue中
- 从Fiber到Root ( scheduleUpdateOnFiber方法中
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
)
- 调度更新(
scheduleUpdateOnFiber方法中ensureRootIsScheduled
)
- render阶段(
performSyncWorkOnRoot
或performConcurrentWorkOnRoot
)
- commit阶段(
commitRoot
)
setState方法会调用 this.updater.enqueueSetState
方法。
this.updater.enqueueSetState(this, partialState, callback, 'setState');
enqueueSetState
enqueueSetState(inst, payload, callback) { const fiber = getInstance(inst);//获取Class组件实例 const eventTime = requestEventTime();//开始调用的时间 const lane = requestUpdateLane(fiber);//获取需要更新的Fiber节点的优先级 const update = createUpdate(eventTime, lane);//根据优先级创建Update对象 update.payload = payload;//赋值需要更新的数据state,也就是setState的第一个参数 if (callback !== undefined && callback !== null) { if (__DEV__) { warnOnInvalidCallback(callback, 'setState'); } update.callback = callback;//回调函数 } enqueueUpdate(fiber, update);//把update入队,存入Fiber节点的updateQueue中(链表) scheduleUpdateOnFiber(fiber, lane, eventTime);//调度update if (__DEV__) { if (enableDebugTracing) { if (fiber.mode & DebugTracingMode) { const name = getComponentName(fiber.type) || 'Unknown'; logStateUpdateScheduled(name, lane, payload); } } } if (enableSchedulingProfiler) { markStateUpdateScheduled(fiber, lane); } }
enqueueForceUpdate
这个方法是
this.forceUpdate
会调用的,除了赋值update.tag = ForceUpdate
以及没有payload
外,其他逻辑与this.setState
一致。这个在判断ClassComponent
是否需要更新时有两个条件需要满足:checkHasForceUpdateAfterProcessing
:内部会判断本次更新的Update是否为ForceUpdate
。即如果本次更新的Update中存在tag为ForceUpdate
,则返回true。
checkShouldComponentUpdate
:内部会调用shouldComponentUpdate
方法。以及当该ClassComponent
为PureComponent
时会浅比较state与props。
useState更新流程
function App() { const [num, updateNum] = useState(0); return <p onClick={() => updateNum(num => num + 1)}>{num}</p>;}
- 通过一些途径产生更新,更新会造成组件render。
- 调用
ReactDOM.render
会产生mount
的更新,更新内容为useState
的initialValue
(即0)。 - 点击p标签触发
updateNum
会产生一次update的更新
,更新内容为num => num + 1
。
- 组件render时useState返回的num为更新后的结果。
- dispatchAction ==> 创建Update并存入触发更新的Fiber节点的pending 环状链表
- 调用scheduleUpdateOnFiber
- render阶段会从当前页面的
根节点向下遍历到子节点
,而触发更新的是APPFunctionCompoent
的Fiber节点
,所以会从当前节点遍历到Root
(从Fiber到root的方法markUpdateLaneFromFiberToRoot)
- 判断当前更新是否是同步
- 获取当前优先级最高的lane,将lanePriority 转换成schedulerPriority
- ensureRootIsScheduled (调度更新),调用
performSyncWorkOnRoot
也就是render阶段
的起点函数并传入优先级,然后进入render、commit阶段
整个流程也就是可以简化成如下公式: > baseState + update1 + update2 =newState
基于baseState和Update链表(再加优先级)计算出新的State