从React源码学习React的工作原理之渲染更新——Render阶段(五)

152 阅读6分钟

知识回顾

        上篇文章我们已经了解到,React渲染更新包含两个阶段:render阶段和commit阶段;render阶段主要是通过调用workLoop函数循环构建workInProgress树,而单个节点的构建工作是由performUnitOfWork函数实现的,这个过程是分为向下遍历和向上回溯,向下遍历由beginWork函数实现,向上回溯由completeWork函数实现。

        接下来,让我们结合源码去了解一下这两个函数到底做了哪些工作,具体的实现是怎样的。

一、workLoop

        workLoop会调用performUnitOfWork函数来执行一个工作单元,performUnitOfWork会调用beginWork来处理当前组件的更新,如果当前组件没有子节点或子节点已经处理完毕,就会调用completeUnitOfWork向上回溯处理组件的副作用、创建DOM树等。

function workLoopSync() {
    while (workInProgress !== null) {
        performUnitOfWork(workInProgress);
    }
}

function workLoopConcurrent() {
    while (workInProgress !== null && !shouldYield()) {
        performUnitOfWork(workInProgress);
    }
}
function performUnitOfWork(unitOfWork: Fiber): void {
    const current = unitOfWork.alternate;
    let next = beginWork(current, unitOfWork, renderLanes);
    unitOfWork.memoizedProps = unitOfWork.pendingProps;
    if (next === null) {
        completeUnitOfWork(unitOfWork);
    } else {
        workInProgress = next;
    }
    ReactCurrentOwner.current = null;
}

二、beginWork

1、职责

        beginWork函数是节点更新的入口,不会直接进行更新操作;它的主要职责包含:

  • diff操作;
    • 调用reconcileChildren函数执行diff操作;
  • 计算更新状态:
    • 函数组件:通过dispatchSetState函数计算新的状态;
    • 类组件:通过getStateFromUpdate函数计算新的状态;
  • 执行render函数;
    • 函数组件:renderWithHooks函数会传入一个Component参数,Component就代表函数组件本身,调用该函数就是执行函数式组件;
    • 类组件:调用instance.render()执行类组件;

2、beginWork函数

function beginWork( 
    current: Fiber | null, // 当前展示的fiber树
    workInProgress: Fiber, // 新构建的fiber树
    renderLanes: Lanes // 渲染优先级
): Fiber | null { 
    if (current !== null) { // current存在,表示非首次渲染
        const oldProps = current.memoizedProps; // 获取旧的props 
        const newProps = workInProgress.pendingProps; // 获取新的props 
        if (oldProps !== newProps) { 
            // 新旧props不同,表示有更新; 
            didReceiveUpdate = true; 
        } else { 
            ... // 省略部分逻辑 
            // 否则,无更新,退出beginWork,并返回子树 
            didReceiveUpdate = false;
            // attemptEarlyBailoutIfNoScheduledUpdate该函数会返回当前节点的子节点,进入下一轮performUnitOfWork
            return attemptEarlyBailoutIfNoScheduledUpdate( 
                current, 
                workInProgress, 
                renderLanes 
            ); 
        } 
    } else { 
        // current为null,表示首次更新 
        didReceiveUpdate = false; 
    } 
    // 根据不同类型执行不同的更新函数 
    switch(workInProgress.tag) { 
        case FunctionComponent: { 
            // 函数组件更新 ...
            return updateFunctionComponent(
                current,
                workInProgress,
                Component,
                resolvedProps,
                renderLanes,
            );
        } 
        case ClassComponent: { 
            // 类组件更新 ... 
            return updateClassComponent( 
                current, 
                workInProgress, 
                Component, 
                resolveProps, 
                renderLanes 
            ); 
        } 
    } 
}

        函数组件更新时,会调用updateFunctionComponent,该函数会根据调用hook更新状态,并且调用reconcileChildren执行diff操作;

function updateFunctionComponent(
    current: null | Fiber,
    workInProgress: Fiber,
    Component: any,
    nextProps: any,
    renderLanes: Lanes,
) {
    // renderWithHooks会调用hook函数初始化与更新state
    let nextChildren = renderWithHooks(
        current,
        workInProgress,
        Component,
        nextProps,
        context,
        renderLanes,
    );

    if (current !== null && !didReceiveUpdate) {
        bailoutHooks(current, workInProgress, renderLanes);
        return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    }
    // 执行diff操作,这是diff的核心函数
    reconcileChildren(current, workInProgress, nextChildren, renderLanes);
    return workInProgress.child;
}

        类组件更新时,则调用updateClassComponent函数,该函数会判断组件实例是否存在,以及是否复用该组件实例;并且会调用processUpdateQueue函数执行更新队列,计算新的组件状态。

function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes,
) {
    const instance = workInProgress.stateNode; // 获取实例对象,只有类组件有实例
    let shouldUpdate;
    if (instance === null) { // 组件实例不存在
        // new ctor构造组件实例 
        constructClassInstance(workInProgress, Component, nextProps);
        // 初始化更新队列,并设置状态
        mountClassInstance(workInProgress, Component, nextProps, renderLanes);
        shouldUpdate = true;
    } else if (current === null) {
        // 中断后继续,则复用组件实例
        shouldUpdate = resumeMountClassInstance(
            workInProgress,
            Component,
            nextProps,
            renderLanes,
        );
    } else {
        // 组件更新:会调用processUpdateQueue函数执行更新队列,计算状态 
        shouldUpdate = updateClassInstance(
            current,
            workInProgress,
            Component,
            nextProps,
            renderLanes,
        );
    }
    // 当前组件执行结束,调用finishClassComponent获取子fiber节点,开启下一轮performUnitOfWork函数
    // 该函数会调用reconcileChildren执行diff操作
    const nextUnitOfWork = finishClassComponent(
        current,
        workInProgress,
        Component,
        shouldUpdate,
        hasContext,
        renderLanes,
    );
    return nextUnitOfWork;
}

三、completeWork

        beginWork结束后,就会进入completeWork阶段,completeUnitOfWork函数是completeWork阶段的入口,它会自下而上遍历workInProgress节点;
        需要注意的是:该阶段主要负责处理fiber节点到DOM节点的映射,生成真实的DOM树,并没有将DOM插入到真实页面上,它只是在操作fiber上的stateNode;真实的插入DOM操作发生在commit阶段;

1、职责

completeWork阶段的职责主要包含以下几方面:

  • 创建DOM节点:
    • 在Fiber节点被创建时,对应的真实DOM节点也会被创建,并被赋值到Fiber的stateNode属性上;
  • 将DOM节点插入到DOM树中:
    • 在completeWork阶段,新创建的DOM节点会被插入到其父节点的子节点列表中,从而逐步构建出整个DOM树;
  • 为DOM节点设置属性:
    • Fiber节点上的属性信息会被应用到对应的真实DOM节点上,包括元素的类名、样式、事件处理器等;
  • 收集副作用:
    • 在completeWork阶段,Fiber节点上的副作用信息会被收集并传递到父节点上,以便在后续的commit阶段进行处理,包括需要更新的状态、需要插入或删除的子节点等;

2、completeWork函数

function completeUnitOfWork(unitOfWork: Fiber): void {
    let completedWork: Fiber = unitOfWork;
    do {
        const current = completedWork.alternate; // 获取当前展示在屏幕上的fiber树
        const returnFiber = completedWork.return; // 获取父节点
        // 非完整代码,只是用于展示completeWork操作
        // 对节点执行completeWork操作
        let next = completeWork(current, completedWork, renderLanes);

        if (next !== null) {
            // 任务被挂起的情况
            workInProgress = next;
            return;
        }

        const siblingFiber = completedWork.sibling;
        if (siblingFiber !== null) {
            // 有兄弟节点,就操作兄弟节点
            workInProgress = siblingFiber;
            return;
        }
        // 否则,操作父节点
        completedWork = returnFiber;
        // Update the next thing we're working on in case something throws.
        workInProgress = completedWork;
    } while (completedWork !== null);

    // 到达根节点
    if (workInProgressRootExitStatus === RootInProgress) {
        workInProgressRootExitStatus = RootCompleted;
    }
}

completeWork函数用于处理属性、绑定事件、生成DOM树:

  • DOM节点的创建:createInstance实现;
  • 属性处理及事件绑定:setInitialProperties实现;
  • props的处理:diffProperties实现;

针对不同的类型节点会有不同的操作,以下是简化后的源码:

function completeWork(
    current: Fiber | null,
    workInProgress: Fiber,
    renderLanes: Lanes,
): Fiber | null {
    const newProps = workInProgress.pendingProps;
    popTreeContext(workInProgress);
    switch (workInProgress.tag) {
        case FunctionComponent: // 针对函数组件
            bubbleProperties(workInProgress);
            return null;
        case ClassComponent: { // 针对类组件
            const Component = workInProgress.type;
            bubbleProperties(workInProgress);
            return null;
        }
        case HostComponent: { // 针对原生DOM节点
            const type = workInProgress.type;
            // current存在,实例存在,表示更新
            if (current !== null && workInProgress.stateNode != null) {
                // 执行更新操作
                updateHostComponent(
                    current,
                    workInProgress,
                    type,
                    newProps,
                    renderLanes,
                );
                // 如果ref变化,标记ref变化
                if (current.ref !== workInProgress.ref) {
                    markRef(workInProgress);
                }
            } else {
                // 创建DOM节点:
                const instance = createInstance(
                    type,
                    newProps,
                    rootContainerInstance,
                    currentHostContext,
                    workInProgress,
                );
                // 插入DOM节点
                appendAllChildren(instance, workInProgress, false, false);
                // 将DOM节点挂载到实例上
                workInProgress.stateNode = instance;
            }

            if (workInProgress.ref !== null) {
                // 如果原生DOM节点存在ref对象,需要开启一次调度
                markRef(workInProgress);
            }
            bubbleProperties(workInProgress);
            return null;
        }
    }
}

总结

        在React中,render阶段是组件更新流程中的一个重要阶段,负责处理组件的渲染逻辑、构建Fiber树、执行diff操作、创建DOM树,在该过程中,每个Fiber节点都会经历从beginWorkcompleteWork的过程,至此,workInProgress树的构建到此结束,接下来,就会进入到React的commit阶段...

beginWork和completeWork过程中会包含很多细节的实现,后续会慢慢补充细节实现!