react 18.2源码解析 - 初次mount - 构建fiber树&挂载真实dom

137 阅读4分钟

ReactElement, Fiber, DOM 三者的关系

  1. 所有采用jsx语法书写的节点, 都会被编译器转换, 最终会使用jsx()的方式, 创建出来一个与之对应的ReactElement对象。
  2. fiber对象是通过ReactElement对象进行创建的, 多个fiber对象构成了一棵fiber树fiber树是构造DOM树的数据模型, fiber树的任何改动, 最后都体现到DOM树
  3. dom对象也就是渲染在页面上的dom元素。
    三者的关系:

image.png

双缓冲技术

current
指向当前已经渲染完成的fiber树

workInProgress
指向正在构建中的fiber树 image.png 构造完成并渲染, 就切换fiberRoot.current指针,让current指针指向workinprogress。

组件类型

每一种vdom都有对应的tag类型,创建时根据ReactElement的type选择。

export const FunctionComponent = 0; //函数组件
export const ClassComponent = 1; //类组件
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; //根节点
export const HostPortal = 4;
export const HostComponent = 5; //原生dom节点
export const HostText = 6; //纯文本节点
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const ScopeComponent = 21;
export const OffscreenComponent = 22;
export const LegacyHiddenComponent = 23;
export const CacheComponent = 24;
export const TracingMarkerComponent = 25;

workloop循环

整个fiber树构造是一个深度优先遍历(可参考React 算法之深度优先遍历)

在深度优先遍历中, 每个fiber节点都会经历2个阶段:

  1. 探寻阶段 beginWork
  2. 回溯阶段 completeWork
function workLoopSync() {
    while (workInProgress !== null) {
        performUnitOfWork(workInProgress)
    }
}

function performUnitOfWork(unitofWork) {
    //获取新fiber对应的老fiber
    const current = unitofWork.alternate
    //完成当前fiber的子fiber联表构建后
    const next = beginWork(current, unitofWork, workInProgressRootRenderLanes)
    unitofWork.memoizedProps = unitofWork.pendingProps
    if (next === null) {
        //没有子节点,表示当前节点构建完成
        completeUnitOfWork(unitofWork)
    } else {
        // 如果有子节点,让子节点成为工作单元
        workInProgress = next
    }
}

beginWork

针对所有的 Fiber 类型, 其中的每一个 case 处理一种 Fiber 类型。
主要逻辑:

  1. 根据 ReactElement对象创建所有的fiber节点, 最终构造出fiber树形结构(设置returnsibling指针)
  2. 设置fiber.flags(二进制形式变量, 用来标记 fiber节点 的增,删,改状态, 等待completeWork阶段处理)
  3. 设置fiber.stateNode局部状态(如Class类型节点: fiber.stateNode=new Class())
export function beginWork(current, workInProgress, renderLanes) {
    // logger(' '.repeat(indent.number) + 'beginWork', workInProgress)
    // indent.number += 2
    switch (workInProgress.tag) {
        case HostRoot:
            return updateHostRoot(current, workInProgress, renderLanes)
        case HostComponent:
            return updateHostComponent(current, workInProgress, renderLanes)
        case HostText:
            return null
        //初次挂载时,函数组件走这个
        //mountIndeterminateComponent中设置workInProgress.tag = FunctionComponent
        //更新时,函数组件走FunctionComponent
        case IndeterminateComponent:
            return mountIndeterminateComponent(
                current,
                workInProgress,
                workInProgress.type,
                renderLanes
            )
        case FunctionComponent:
            const Component = workInProgress.type
            const nextProps = workInProgress.pendingProps
            return updateFunctionComponent(
                current,
                workInProgress,
                Component,
                nextProps,
                renderLanes
            )
        default:
            return null
    }
}

complateWork

主要工作

  1. 调用completeWork

    • fiber节点(tag=HostComponent, HostText)创建 DOM 实例, 设置fiber.stateNode局部状态(如tag=HostComponent, HostText节点: fiber.stateNode 指向这个 DOM 实例).
    • 为 DOM 节点设置属性, 绑定事件(这里先说明有这个步骤, 详细的事件处理流程, 在合成事件原理中详细说明).
    • 设置fiber.flags标记
  2. 把当前 fiber 对象的副作用队列(firstEffectlastEffect)添加到父节点的副作用队列之后, 更新父节点的firstEffectlastEffect指针.

  3. 识别beginWork阶段设置的fiber.flags, 判断当前 fiber 是否有副作用(增,删,改), 如果有, 需要将当前 fiber 加入到父节点的effects队列, 等待commit阶段处理.

function completeUnitOfWork(unitofWork) {
    let completedWork = unitofWork
    do {
        const current = completedWork.alternate
        const returnFiber = completedWork.return
        //执行此fiber的完成工作
        //如果是原生组件,创建真实的dom组件
        completeWork(current, completedWork)
        const siblingFiber = completedWork.sibling
        //如果有弟弟,构建弟弟的fiber子链表
        if (siblingFiber !== null) {
            workInProgress = siblingFiber
            return
        }
        //如果没有弟弟,说明这当前完成的就是父fiber的最后一个节点
        //也就说明父fiber的子链表构建完成,也就是父fiber完成了
        completedWork = returnFiber
        workInProgress = completedWork
    } while (completedWork !== null)
    // 如果走到这里,说明整个fiber树全部构建完成
    if (workInProgressRootExitStatus === RootInProgress) {
        workInProgressRootExitStatus = RootCompleted
    }
}

commitRoot

  1. 处理副作用队列(增删改).
  2. 调用渲染器, 输出最终结果(挂载真实dom).
function commitRootImpl(root) {
    //获取新的构建好的fiber树的根节点
    const { finishedWork } = root
    workInProgressRoot = null
    workInProgressRootRenderLanes = NoLanes
    root.callbackNode = null
    root.callbackPriority = null
    // printFinishedWork(finishedWork)
    if (
        (finishedWork.subtreeFlags & Passive) !== NoFlags ||
        (finishedWork.flags & Passive) !== NoFlags
    ) {
        if (!rootDoesHavePassiveEffects) {
            rootDoesHavePassiveEffects = true
            scheduleCallback(NormalSchedulerPriority, flushPassiveEffects)
        }
    }
    // console.log('开始commit~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
    //subtreeHasEffects中包含添加或更新
    const subtreeHasEffects =
        (finishedWork.subtreeFlags & MutationMask) !== NoFlags
    //flags中包含添加或更新
    const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags
    //如果自己有副作用或者子节点有副作用,就进行提交操作
    if (subtreeHasEffects || rootHasEffect) {
        // console.log(
        //     'dom执行变更 - commitMutationEffectsOnFiber~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
        // )
        //dom执行变更之后
        commitMutationEffectsOnFiber(finishedWork, root)
        console.log(
            'dom执行变更后 - commitLayoutEffects~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
        )
        // 执行layoutEffect
        commitLayoutEffects(finishedWork, root)

        if (rootDoesHavePassiveEffects) {
            rootDoesHavePassiveEffects = false
            rootWithPendingPassiveEffects = root
        }
    }
    // dom变更后,让root的current指向新的fiber树
    root.current = finishedWork
    // root.pendingLanes = 16
    // ensureRootIsScheduled(root)
}

本文参考和引用文章列表:(如有侵权,请告知删除)
github.com/7kms/react-…