Little-Gee / blog

14 stars 11 forks source link

6.从触发状态更新开始 #23

Open Little-Gee opened 1 year ago

Little-Gee commented 1 year ago

一开始我们讲了整个渲染的简略流程,之后又梳理了几个关键节点,现在我们深入每个关键节点看看它们到底做了什么。

第一个节点是触发状态更新,之前举的例子是应用的启动,其实这只是触发状态更新的方式之一。 在 React 中,有以下几种触发状态更新的方法:

  1. ReactDOM.render / ReactDOM.createRoot
  2. this.setState
  3. this.forceUpdate
  4. useState
  5. useReducer

    归类

    看上去有好几种,实际上我们可以给他们归归类:

发起组件更新这一类型其实还可以根据组件类型分成两类:ClassComponentFunctionComponent,而应用初始化这种类型对应的组件是HostRoot,所以以组件类型来划分的话,就是下面这样:

(这么划分是因为后面的update对象有两种类型)

虽然这几种方式看起来使用场景不同,实际上内部流程中有很多相似之处。

1.HostRoot

应用的启动中我们在最后讲到更新的时候会调用updateContainer这个函数:

export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
    // 省略

    // 获取当前时间戳
    const current = container.current;
    const eventTime = requestEventTime();

    // 省略

    // 1.创建一个优先级变量(车道模型)
    const lane = requestUpdateLane(current);

    // 省略

    const context = getContextForSubtree(parentComponent);
    if (container.context === null) {
        container.context = context;
    } else {
        container.pendingContext = context;
    }

    // 省略

    // 2.根据车道优先级,创建 update 对象
    const update = createUpdate(eventTime, lane);
    update.payload = {element};

    // callback为 ReactDOM.render 的第三个参数 —— 回调函数
    callback = callback === undefined ? null : callback;
    if (callback !== null) {
        // 省略
        update.callback = callback;
    }

    // 3.将生成的 update 加入 updateQueue
    enqueueUpdate(current, update);
    // 4.调度更新
    scheduleUpdateOnFiber(current, lane, eventTime);

    return lane;
}

可以看到里面包含4个步骤:

  1. 创建一个优先级变量
  2. 根据优先级,创建 update 对象
  3. 将生成的 update 加入 updateQueue
  4. 调度更新

    2.ClassComponent

    我们再来看看ClassComponent的更新:

    const classComponentUpdater = {
    isMounted,
    enqueueSetState(inst, payload, callback) {
        const fiber = getInstance(inst);
        const eventTime = requestEventTime();
        const lane = requestUpdateLane(fiber);
    
        const update = createUpdate(eventTime, lane);
        update.payload = payload;
        if (callback !== undefined && callback !== null) {
            // 省略
    
            update.callback = callback;
        }
    
        enqueueUpdate(fiber, update);
        scheduleUpdateOnFiber(fiber, lane, eventTime);
    
        // 省略
    
        if (enableSchedulingProfiler) {
            markStateUpdateScheduled(fiber, lane);
        }
    },
    enqueueReplaceState(inst, payload, callback) {
        const fiber = getInstance(inst);
        const eventTime = requestEventTime();
        const lane = requestUpdateLane(fiber);
    
        const update = createUpdate(eventTime, lane);
        update.tag = ReplaceState;
        update.payload = payload;
    
        if (callback !== undefined && callback !== null) {
            // 省略
    
            update.callback = callback;
        }
    
        enqueueUpdate(fiber, update);
        scheduleUpdateOnFiber(fiber, lane, eventTime);
    
        // 省略
    
        if (enableSchedulingProfiler) {
            markStateUpdateScheduled(fiber, lane);
        }
    },
    enqueueForceUpdate(inst, callback) {
        const fiber = getInstance(inst);
        const eventTime = requestEventTime();
        const lane = requestUpdateLane(fiber);
    
        const update = createUpdate(eventTime, lane);
        update.tag = ForceUpdate;
    
        if (callback !== undefined && callback !== null) {
            // 省略
    
            update.callback = callback;
        }
    
        enqueueUpdate(fiber, update);
        scheduleUpdateOnFiber(fiber, lane, eventTime);
    
        // 省略
    
        if (enableSchedulingProfiler) {
            markForceUpdateScheduled(fiber, lane);
        }
    },
    };

    里面有enqueueSetStateenqueueReplaceStateenqueueForceUpdate三个方法,从名字就可以看出对应setStatereplaceStateforceUpdateenqueueReplaceState是在内部用的,可以不管)。 setStateforceUpdate的代码如下:

    
    Component.prototype.setState = function(partialState, callback) {
    // 省略
    
    this.updater.enqueueSetState(this, partialState, callback, 'setState');
    };

Component.prototype.forceUpdate = function(callback) { this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); };

可以看到这两个是原型链上的方法,内部分别调用了`enqueueSetState`和`enqueueForceUpdate`。
而在上面的具体方法中,我们又可以看到这几个方法都是差不多的,也是`创建优先级变量 -> 创建 update 对象 -> update 加入 updateQueue -> 调度更新`这个流程。
## 3.FunctionComponent
最后再来看看`FunctionComponent`的更新:
```javascript
function dispatchAction<S, A>(
    fiber: Fiber,
    queue: UpdateQueue<S, A>,
    action: A,
) {
    // 省略

    const eventTime = requestEventTime();
    const lane = requestUpdateLane(fiber);

    // 创建 update对象
    const update: Update<S, A> = {
        lane,
        action,
        eagerReducer: null,
        eagerState: null,
        next: (null: any),
    };

    // 将 update对象 添加到 hook.queue.pending 队列
    const pending = queue.pending;
    if (pending === null) {
        // 首个 update,创建一个环形链表
        update.next = update;
    } else {
        update.next = pending.next;
        pending.next = update;
    }
    queue.pending = update;

    const alternate = fiber.alternate;
    if (
        fiber === currentlyRenderingFiber ||
        (alternate !== null && alternate === currentlyRenderingFiber)
    ) {
        // 渲染时更新,做好全局标记
        didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
    } else {
        // fiber 的 updateQueue 为空,优化路径
        // 下面这个 if 判断, 能保证当前创建的 update 是 queue.pending 中第一个 update
        // 因为发起更新之后 fiber.lanes 会被改动,如果 fiber.lanes 和 alternate.lanes 没有被改动,自然就是首个 update
        if (
            fiber.lanes === NoLanes &&
            (alternate === null || alternate.lanes === NoLanes)
        ) {
            const lastRenderedReducer = queue.lastRenderedReducer;
            if (lastRenderedReducer !== null) {
                let prevDispatcher;

                // 省略

                try {
                    const currentState: S = (queue.lastRenderedState: any);
                    const eagerState = lastRenderedReducer(currentState, action);
                    // 将 eagerState 和 eagerReducer 保存在 update 上
                    // 如果在 render 阶段 reducer == update.eagerReducer,则可以直接使用无需再次计算
                    update.eagerReducer = lastRenderedReducer;
                    update.eagerState = eagerState;
                    if (is(eagerState, currentState)) {
                        // 无需调度更新
                        // 注:update 已经被添加到了 queue.pending,并没有丢弃。之后需要更新的时候,此 update 还是会起作用
                        return;
                    }
                } catch (error) {
                } finally {
                    // 省略
                }
            }
        }

        // 发起调度更新
        scheduleUpdateOnFiber(fiber, lane, eventTime);
    }

    if (enableSchedulingProfiler) {
        markStateUpdateScheduled(fiber, lane);
    }
}

对于FunctionComponent,发起更新的方法是调用useStateuseReducer返回的方法。 例如const [num, updateNum] = useState(0);,调用updateNum(1)就可以更新num。 其实useState只是useReducer的一种特殊情况,具体的放在后面hook的章节中讲,这里只要知道它们内部最终调用的是dispatchAction就行。 看一下dispatchAction,可以发现其实也是创建优先级变量 -> 创建 update 对象 -> update 加入 updateQueue -> 调度更新这个流程。

小结

从上面的分析中可以看到,触发三种类型组件更新的方法都有着相似的流程:

  1. 创建一个优先级变量
  2. 根据优先级,创建 update 对象
  3. 将生成的 update 加入 updateQueue
  4. 调度更新

yuque_diagram

流程分析

我们继续看看每一个步骤是怎么做的。

1.创建一个优先级变量

这个步骤比较统一,用的都是同一个方法requestUpdateLane

export function requestUpdateLane(fiber: Fiber): Lane {
    const mode = fiber.mode;
    if ((mode & BlockingMode) === NoMode) {
        // legacy 模式
        return (SyncLane: Lane);
    } else if ((mode & ConcurrentMode) === NoMode) {
        // blocking 模式
        return getCurrentPriorityLevel() === ImmediateSchedulerPriority
            ? (SyncLane: Lane)
            : (SyncBatchedLane: Lane);
    } else if (
        !deferRenderPhaseUpdateToNextBatch &&
        (executionContext & RenderContext) !== NoContext &&
        workInProgressRootRenderLanes !== NoLanes
    ) {
        return pickArbitraryLane(workInProgressRootRenderLanes);
    }

    // concurrent 模式
    if (currentEventWipLanes === NoLanes) {
        currentEventWipLanes = workInProgressRootIncludedLanes;
    }

    const isTransition = requestCurrentTransition() !== NoTransition;
    if (isTransition) {
        // 特殊情况,处于 suspense 过程中
        if (currentEventPendingLanes !== NoLanes) {
            currentEventPendingLanes =
                mostRecentlyUpdatedRoot !== null
                    ? mostRecentlyUpdatedRoot.pendingLanes
                    : NoLanes;
        }
        return findTransitionLane(currentEventWipLanes, currentEventPendingLanes);
    }

    // 正常情况,获取调度优先级
    const schedulerPriority = getCurrentPriorityLevel();

    let lane;
    if (
        (executionContext & DiscreteEventContext) !== NoContext &&
        schedulerPriority === UserBlockingSchedulerPriority
    ) {
        // executionContext 存在输入事件,且调度优先级是用户阻塞性质
        lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
    } else {
        // 调度优先级转换为车道模型
        const schedulerLanePriority = schedulerPriorityToLanePriority(
            schedulerPriority,
        );

        // 省略

        lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
    }

    return lane;
}

在不同模式下会返回不同的优先级:

yuque_diagram (1)

2.根据优先级,创建 update 对象

在创建updata对象这一步,HostRootClassComponent是一种结构,通过createUpdate这个函数创建;FunctionComponent是在dispatchAction函数中直接创建的,所以update是有两种结构的。

export function createUpdate(eventTime: number, lane: Lane): Update<*> {
    const update: Update<*> = {
        eventTime,
        lane,

        tag: UpdateState,
        payload: null,
        callback: null,

        next: null,
    };
    return update;
}
function dispatchAction<S, A>(
    fiber: Fiber,
    queue: UpdateQueue<S, A>,
    action: A,
) {
    // 省略

    // 创建 update对象
    const update: Update<S, A> = {
        lane,
        action,
        eagerReducer: null,
        eagerState: null,
        next: (null: any),
    };

    // 省略
}

yuque_diagram (2)

3.将生成的 update 加入链表中

上一步创建了update对象,这一步是将其加入updateQueue或者是hook.queue.pending中:

export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
    const updateQueue = fiber.updateQueue;
    if (updateQueue === null) {
        return;
    }

    const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
    const pending = sharedQueue.pending;
    if (pending === null) {
        // 第一个 update
        update.next = update;
    } else {
        update.next = pending.next;
        pending.next = update;
    }
    sharedQueue.pending = update;

    // 省略
}
function dispatchAction<S, A>(
    fiber: Fiber,
    queue: UpdateQueue<S, A>,
    action: A,
) {
    // 省略

    // 创建 update对象
    const update: Update<S, A> = {
        lane,
        action,
        eagerReducer: null,
        eagerState: null,
        next: (null: any),
    };

    // 将 update对象 添加到 hook.queue.pending 队列
    const pending = queue.pending;
    if (pending === null) {
        // 首个 update,创建一个环形链表
        update.next = update;
    } else {
        update.next = pending.next;
        pending.next = update;
    }
    queue.pending = update;

    // 省略
}

可以看到虽然一种是调用了enqueueUpdate方法,另一种是直接入队,但是逻辑都是一样的,updateQueuehook.queue.pending都是环形链表,同时,为了方便入队和拿到队首元素,pending指向链表中最后一个元素。 yuque_diagram (3)

4.调度更新

最后就是发起调度更新了,这一步比较统一,都是调用scheduleUpdateOnFiber

export function scheduleUpdateOnFiber(
    fiber: Fiber,
    lane: Lane,
    eventTime: number,
) {
    checkForNestedUpdates();
    warnAboutRenderPhaseUpdatesInDEV(fiber);

    // 标记优先级
    const root = markUpdateLaneFromFiberToRoot(fiber, lane);
    if (root === null) {
        warnAboutUpdateOnUnmountedFiberInDEV(fiber);
        return null;
    }

    markRootUpdated(root, lane, eventTime);

    if (root === workInProgressRoot) {
        if (
            deferRenderPhaseUpdateToNextBatch ||
            (executionContext & RenderContext) === NoContext
        ) {
            workInProgressRootUpdatedLanes = mergeLanes(
                workInProgressRootUpdatedLanes,
                lane,
            );
        }
        if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
            markRootSuspended(root, workInProgressRootRenderLanes);
        }
    }

    const priorityLevel = getCurrentPriorityLevel();

    if (lane === SyncLane) {
        if (
            (executionContext & LegacyUnbatchedContext) !== NoContext &&
            (executionContext & (RenderContext | CommitContext)) === NoContext
        ) {
            schedulePendingInteractions(root, lane);

            // 首次渲染, 直接进行 fiber构造
            performSyncWorkOnRoot(root);
        } else {
            // 对比更新,注册回调任务
            ensureRootIsScheduled(root, eventTime);
            schedulePendingInteractions(root, lane);
            if (executionContext === NoContext) {
                resetRenderTimer();
                // 如果执行上下文为空,取消 schedule 调度,主动刷新回调队列
                flushSyncCallbackQueue();
            }
        }
    } else {
        if (
            (executionContext & DiscreteEventContext) !== NoContext &&
            (priorityLevel === UserBlockingSchedulerPriority ||
                priorityLevel === ImmediateSchedulerPriority)
        ) {
            if (rootsWithPendingDiscreteUpdates === null) {
                rootsWithPendingDiscreteUpdates = new Set([root]);
            } else {
                rootsWithPendingDiscreteUpdates.add(root);
            }
        }
        ensureRootIsScheduled(root, eventTime);
        schedulePendingInteractions(root, lane);
    }

    mostRecentlyUpdatedRoot = root;
}

这个函数是承接输入的入口。渲染器可以不同,但是在react-reconciler中,想要改变fiber的操作,都会经过scheduleUpdateOnFiber,从此处开始就是进度任务调度流程了。 yuque_diagram (4)