优先级相关

内容在SchedulePriorities.js

  • NoPrioritiy: 初始化时无优先级
  • ImmediatePrioritiy: 立刻执行的优先级 (也就是同步执行的优先级)
  • UserBlockingPrioritiy: 用户触发的更新优先级,如点击事件等等的优先级
  • NormalPrioritiy:一般的更新级,请求数据返回时更新状态
  • LowPrioritiy:Suspence使用的
  • IdlePrioritiy: 空闲时的优先级

状态更新

每次状态更新都会创建一个保存更新状态相关内容的Update对象。在render阶段beginWork方法中会根据Update计算新的state。

  1. 触发状态更新(根据场景调用不同方法)
  2. 创建Update对象

    1. ReactDOM.render
    2. this.setState
    3. this.forceUpdate
    4. useState
    5. useReducer
  3. 从fiber到root(markUpdateLaneFromFiberToRoot
  4. 调度更新(ensureRootIsScheduled):首先如果rootFiber对应的Fiber树中某个Fiber节点包含一个Update,然后通知Scheduler根据更新的优先级,决定以同步还是异步的方式调度本次更新
  5. render阶段(performSyncWorkOnRootperformConcurrentWorkOnRoot
  6. commit阶段(commitRoot方法,传入rootFiber)

Update结构

ClassComponent 和 HostRoot 共用一套Update结构

Update由createUpdate方法返回,可以从这里看到createUpdate的源码

Fiber节点上的多个Update会组成链表并被包含在fiber.updateQueue中。

什么情况下一个Fiber节点会存在多个Update?如下就是一种:

  1. onClick() {
  2. this.setState({
  3. a: 1
  4. })
  5. this.setState({
  6. b: 2
  7. })
  8. }

在一个ClassComponent中触发onClick方法,方法内部调用了两次this.setState。这会在该Fiber节点中产生两个Update

  1. const update: Update<*> = {
  2. eventTime,//发起update事件的时间,performance.now()获取毫秒时间(17.0.2中作为临时字段, 即将移出)
  3. lane,// update所属的优先级
  4. tag: UpdateState,//更新的类型 UpdateState/ReplaceState /ForceUpdate/CaptureUpdate
  5. payload: null, // 更新挂载的数据,不同类型组件挂载的数据不同。HostRoot为需要挂载在根节点的组件,也就是ReactDom.render的第一个参数,而ClassComponent为this.setState的第一个参数
  6. callback: null,//更新的回调函数,commit完成之后会调用。也就是ClassComponent中this.setState的回调函数,也就是第二个参数、HostRoot中render的第三个参数
  7. next: null,//指向下一个update对象,形成链表。如高优打断低优的任务,或者同一个事件中多次触发setState会创建多个Update对象依次连接。由于UpdateQueue是一个环形链表, 最后一个update.next指向第一个update对象.
  8. };
  9. const queue: UpdateQueue<State> = {
  10. baseState: fiber.memoizedState,
  11. firstBaseUpdate: null,
  12. lastBaseUpdate: null,
  13. shared: {
  14. pending: null,
  15. },
  16. effects: null,
  17. };
  18. fiber.updateQueue = queue;
  19. //=========UpdateQueue==========
  20. type SharedQueue<State> = {|
  21. pending: Update<State> | null,//指向即将输入的update队列. 在class组件中调用setState()之后, 会将新的 update 对象添加到这个队列中来.
  22. |};
  23. export type UpdateQueue<State> = {|
  24. baseState: State,
  25. firstBaseUpdate: Update<State> | null,
  26. lastBaseUpdate: Update<State> | null,
  27. shared: SharedQueue<State>,
  28. effects: Array<Update<State>> | null,//用于保存有callback回调函数的 update 对象, 在commit之后, 会依次调用这里的回调函数.
  29. |};

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结构

Hook对象的数据结构

  1. //除memoizedState以外字段的意义ClassComponent的 updateQueue类似
  2. export type Hook = {|
  3. memoizedState: any,
  4. baseState: any,
  5. baseQueue: Update<any, any> | null,
  6. queue: UpdateQueue<any, any> | null,
  7. next: Hook | null,
  8. |};
  9. type Update<S, A> = {|
  10. lane: Lane,
  11. action: A,
  12. eagerReducer: ((S, A) => S) | null,
  13. eagerState: S | null,
  14. next: Update<S, A>,
  15. priority?: ReactPriorityLevel,
  16. |};
  17. type UpdateQueue<S, A> = {|
  18. pending: Update<S, A> | null,
  19. dispatch: (A => mixed) | null,
  20. lastRenderedReducer: ((S, A) => S) | null,
  21. lastRenderedState: S | null,
  22. |};

hook与FunctionComponent fiber都存在memoizedState属性,

  • FunctionComponent中的UpdateQueue

updateQueue工作流

假设有一个Fiber节点刚刚完成commit阶段的渲染,这个Fiber节点上存在两个由于优先级过低并没有被更新的Update(update1,update2),就会成为下次更新的baseUpdate,也就是render阶段跳过了这两个update处理。

  1. fiber.updateQueue.firstBaseUpdate = update1;
  2. fiber.updateQueue.lastBaseUpdate = update2;
  3. update1.next = update2;

链表指向:fiber.updateQueue.baseUpdate: update1->update2

然后在Fiber上触发两次状态更新,这会先后产生两个新的Update(update3,update4)

enqueueUpdate

就是为Fiber节点增加新的Update的方法

首先获取到shared.pending,如果存在update,就把新的update.next指向pending.nextpending.next指向新的update,形成环状链表

  1. const pending = sharedQueue.pending;
  2. if (pending === null) {
  3. // This is the first update. Create a circular list.
  4. update.next = update;
  5. } else {
  6. update.next = pending.next;
  7. pending.next = update;
  8. }
  9. sharedQueue.pending = update;

每个 Update 都会通过 enqueueUpdate 方法插入到 Fiber的updateQueue.shared.pending上

  1. fiber.updateQueue.shared.pending === update3;
  2. update3.next === update3;

也就是shared.pending是保存了update3的环形链表

插入update4后:

  1. fiber.updateQueue.shared.pending === update4;
  2. update4.next = update3;
  3. update3.next = update4

环形链表指向 shared.pending:update4->update3->update4

假如还有update5:

  1. fiber.updateQueue.shared.pending === update5;
  2. update5.next = update3
  3. update4.next = update5;

环形链表指向 shared.pending:update5->update3->update4->update5

shared.pending 会保证始终指向最后一个插入的update

更新调度完成后进入render阶段,这个时候shared.pending的环被剪开并连接在updateQueue.lastBaseUpdate后面:

processUpdateQueue

  1. export function processUpdateQueue<State>(
  2. workInProgress: Fiber,
  3. props: any,
  4. instance: any,
  5. renderLanes: Lanes,
  6. ): void {
  7. // This is always non-null on a ClassComponent or HostRoot
  8. const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
  9. let firstBaseUpdate = queue.firstBaseUpdate;
  10. let lastBaseUpdate = queue.lastBaseUpdate;
  11. // Check if there are pending updates. If so, transfer them to the base queue.
  12. let pendingQueue = queue.shared.pending;
  13. if (pendingQueue !== null) {
  14. queue.shared.pending = null;
  15. // The pending queue is circular. Disconnect the pointer between first
  16. // and last so that it's non-circular.
  17. const lastPendingUpdate = pendingQueue;
  18. const firstPendingUpdate = lastPendingUpdate.next;
  19. lastPendingUpdate.next = null;
  20. // Append pending updates to base queue
  21. if (lastBaseUpdate === null) {
  22. firstBaseUpdate = firstPendingUpdate;
  23. } else {
  24. lastBaseUpdate.next = firstPendingUpdate;
  25. }
  26. lastBaseUpdate = lastPendingUpdate;
  27. }
  28. }

render阶段的这个方法就是根据queue.shared.pending!==null来判断
是否有新的更新,如果有的话,会把他们转入baseQueue:

fiber.updateQueue.baseUpdate:update1->update2->update3->update4

React流程概览 - 图1

接下来遍历updateQueue.baseUpdate链表,以fiber.updateQueue.baseState为初始state,依次与遍历到的每个Update计算并产生新的state。

在遍历时如果有优先级低的Update会被跳过。

当遍历完成后获得的state会保存在Fiber节点的memoizedState属性上,就是该Fiber节点在本次更新的state

比如说updateCount设置的值依赖于上一次count的值,React怎么能够获取上次的值呢?

  1. const onClick = () =>{
  2. updateCount(count => count + 1)
  3. }

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

React流程概览 - 图2

创建Fiber

ReactDOM.render方法会调用legacyRenderSubtreeIntoContainer方法创建FiberRootNode

legacyRenderSubtreeIntoContainer:

  1. let root: RootType = (container._reactRootContainer: any);
  2. let fiberRoot;
  3. if (!root) {
  4. // Initial mount
  5. //container是挂载的节点,ReactDOM.render的第二个参数
  6. root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
  7. container,
  8. forceHydrate,
  9. );
  10. fiberRoot = root._internalRoot;//创建FiberRoot
  11. .....

其中legacyCreateRootFromDOMContainer方法最终调用的createRootImpl方法,其中createContainer方法调用了createFiberRoot方法,这里面真正创建了fiberRootNoderootFiber以及进行两者的关联,最后初始化了updateQueue:

createRootImpl

createFiberRoot

  1. export function createFiberRoot(
  2. containerInfo: any,
  3. tag: RootTag,
  4. hydrate: boolean,
  5. hydrationCallbacks: null | SuspenseHydrationCallbacks,
  6. ): FiberRoot {
  7. const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
  8. if (enableSuspenseCallback) {
  9. root.hydrationCallbacks = hydrationCallbacks;
  10. }
  11. // Cyclic construction. This cheats the type system right now because
  12. // stateNode is any.
  13. const uninitializedFiber = createHostRootFiber(tag);
  14. root.current = uninitializedFiber;
  15. uninitializedFiber.stateNode = root;
  16. initializeUpdateQueue(uninitializedFiber);
  17. return root;
  18. }

创建update

做好了组件的初始化工作后,接下来就等待创建Update来开启一次更新。updateContainer

  1. export function updateContainer(
  2. element: ReactNodeList,
  3. container: OpaqueRoot,
  4. parentComponent: ?React$Component<any, any>,
  5. callback: ?Function,
  6. ): Lane {
  7. // ...
  8. // 创建update
  9. const update = createUpdate(eventTime, lane, suspenseConfig);
  10. // update.payload为需要挂载在根节点的组件,也就是说对于HostRoot,payload为ReactDOM.render的第一个传参
  11. update.payload = {element};
  12. // callback为ReactDOM.render的第三个参数 —— 回调函数
  13. callback = callback === undefined ? null : callback;
  14. if (callback !== null) {
  15. update.callback = callback;
  16. }
  17. // 将生成的update加入updateQueue
  18. enqueueUpdate(current, update);
  19. // 调度更新
  20. scheduleUpdateOnFiber(current, lane, eventTime);
  21. // ...
  22. }

流程如下:

  • 创建FiberRootNode、rootFiber、初始化updateQueue(legacyCreateRootFromDOMContainer==>createRootImpl==>createFiberRoot)
  • 创建Update对象(updateContainer),放入fiber节点的updateQueue中
  • 从Fiber到Root ( scheduleUpdateOnFiber方法中const root = markUpdateLaneFromFiberToRoot(fiber, lane);)
  • 调度更新(scheduleUpdateOnFiber方法中ensureRootIsScheduled
  • render阶段(performSyncWorkOnRootperformConcurrentWorkOnRoot)
  • commit阶段(commitRoot)

setState方法会调用 this.updater.enqueueSetState方法。

  1. this.updater.enqueueSetState(this, partialState, callback, 'setState');

enqueueSetState

  1. enqueueSetState(inst, payload, callback) {
  2. const fiber = getInstance(inst);//获取Class组件实例
  3. const eventTime = requestEventTime();//开始调用的时间
  4. const lane = requestUpdateLane(fiber);//获取需要更新的Fiber节点的优先级
  5. const update = createUpdate(eventTime, lane);//根据优先级创建Update对象
  6. update.payload = payload;//赋值需要更新的数据state,也就是setState的第一个参数
  7. if (callback !== undefined && callback !== null) {
  8. if (__DEV__) {
  9. warnOnInvalidCallback(callback, 'setState');
  10. }
  11. update.callback = callback;//回调函数
  12. }
  13. enqueueUpdate(fiber, update);//把update入队,存入Fiber节点的updateQueue中(链表)
  14. scheduleUpdateOnFiber(fiber, lane, eventTime);//调度update
  15. if (__DEV__) {
  16. if (enableDebugTracing) {
  17. if (fiber.mode & DebugTracingMode) {
  18. const name = getComponentName(fiber.type) || 'Unknown';
  19. logStateUpdateScheduled(name, lane, payload);
  20. }
  21. }
  22. }
  23. if (enableSchedulingProfiler) {
  24. markStateUpdateScheduled(fiber, lane);
  25. }
  26. }

enqueueForceUpdate

这个方法是this.forceUpdate会调用的,除了赋值update.tag = ForceUpdate以及没有payload外,其他逻辑与this.setState一致。这个在判断ClassComponent是否需要更新时有两个条件需要满足:

  • checkHasForceUpdateAfterProcessing:内部会判断本次更新的Update是否为ForceUpdate。即如果本次更新的Update中存在tag为ForceUpdate,则返回true。
  • checkShouldComponentUpdate:内部会调用shouldComponentUpdate方法。以及当该ClassComponentPureComponent时会浅比较state与props。

useState更新流程

  1. function App() {
  2. const [num, updateNum] = useState(0);
  3. return <p onClick={() => updateNum(num => num + 1)}>{num}</p>;
  4. }
  1. 通过一些途径产生更新,更新会造成组件render。

    1. 调用ReactDOM.render会产生mount的更新,更新内容为useStateinitialValue(即0)。
    2. 点击p标签触发updateNum会产生一次update的更新,更新内容为num => num + 1
  2. 组件render时useState返回的num为更新后的结果。
  • dispatchAction ==> 创建Update并存入触发更新的Fiber节点的pending 环状链表
  • 调用scheduleUpdateOnFiber

    • render阶段会从当前页面的根节点向下遍历到子节点 ,而触发更新的是APP FunctionCompoentFiber节点,所以会从当前节点遍历到Root (从Fiber到root的方法markUpdateLaneFromFiberToRoot)
  • 判断当前更新是否是同步
  • 获取当前优先级最高的lane,将lanePriority 转换成schedulerPriority
  • ensureRootIsScheduled (调度更新),调用performSyncWorkOnRoot 也就是render阶段的起点函数并传入优先级,然后进入render、commit阶段

整个流程也就是可以简化成如下公式:

baseState + update1 + update2 =newState

基于baseState和Update链表(再加优先级)计算出新的State