Skip to main content

React的初次渲染

React 的初次渲染

  1. 通过 babel 编译将写的 html 标签转换成 jsx 函数,执行 jsx 函数生成虚拟 dom

  1. 调用 creatRoot 方法会创建一个 ReactDOMRoot 一个实例
    1. 其中会创建根 fiber,进行事件机制的绑定 初始化跟新队列
const root = createRoot(document.getElementById("root"))

  1. 调用 root 的 render 方法先把 element 虚拟 dom 放到 HostRootFiber 的更新队列中

    • 这是一个循环链表

  2. 开始调度更新,初始化栈(初次未空,更新的时候会给 fiber 的 update 赋值)

    //根节点调度跟新
    scheduleUpdateOnFiber(root, current, lane, eventTime)

    // 并发渲染
    function renderRootConcurrent(root, lanes) {
    //因为在构建fiber树的过程中,此方法会反复进入,会进入多次
    //只有在第一次进来的时候会创建新的fiber树,或者说新fiber
    if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    prepareFreshStack(root, lanes)
    }
    //在当前分配的时间片(5ms)内执行fiber树的构建或者说渲染,
    // 开始工作循环
    workLoopConcurrent()
    //如果 workInProgress不为null,说明fiber树的构建还没有完成
    if (workInProgress !== null) {
    return RootInProgress
    }
    //如果workInProgress是null了说明渲染工作完全结束了
    return workInProgressRootExitStatus
    }

    // 构建workInProgress fiber
    function prepareFreshStack(root, renderLanes) {
    workInProgress = createWorkInProgress(root.current, null)
    workInProgressRootRenderLanes = renderLanes
    workInProgressRoot = root
    finishQueueingConcurrentUpdates()
    }

    export function finishQueueingConcurrentUpdates() {
    const endIndex = concurrentQueuesIndex //9 只是一边界条件
    concurrentQueuesIndex = 0
    let i = 0
    while (i < endIndex) {
    const fiber = concurrentQueues[i++]
    const queue = concurrentQueues[i++]
    const update = concurrentQueues[i++]
    const lane = concurrentQueues[i++]
    if (queue !== null && update !== null) {
    const pending = queue.pending
    if (pending === null) {
    update.next = update
    } else {
    update.next = pending.next
    pending.next = update
    }
    queue.pending = update
    }
    }
    }
  3. 开始递归构建 fiber 树,执行工作单元

    /**
    * 执行一个工作单元
    * @param {*} unitOfWork
    */
    function performUnitOfWork(unitOfWork) {
    //获取新的fiber对应的老fiber
    const current = unitOfWork.alternate
    //完成当前fiber的子fiber链表构建后
    const next = beginWork(current, unitOfWork, workInProgressRootRenderLanes)
    unitOfWork.memoizedProps = unitOfWork.pendingProps
    if (next === null) {
    //如果没有子节点表示当前的fiber已经完成了
    completeUnitOfWork(unitOfWork)
    } else {
    //如果有子节点,就让子节点成为下一个工作单元
    workInProgress = next
    }
    }
  4. 调用 beginWork 方法 ,根据老 fiber 和新的虚拟 dom 构建新 fiber(diff 算法)

    /**
    * 目标是根据新虚拟DOM构建新的fiber子链表
    * @param {*} current 老fiber
    * @param {*} workInProgress 新的fiber h1
    * @returns
    */
    export function beginWork(current, workInProgress, renderLanes) {
    //在构建fiber树之后清空lanes
    workInProgress.lanes = 0
    switch (workInProgress.tag) {
    // 因为在React里组件其实有两种,一种是函数组件,一种是类组件,但是它们都是都是函数
    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)
    }
    case HostRoot:
    return updateHostRoot(current, workInProgress, renderLanes)
    case HostComponent:
    return updateHostComponent(current, workInProgress, renderLanes)
    case HostText:
    return null
    default:
    return null
    }
    }

    /**
    * 构建原生组件的子fiber链表
    * @param {*} current 老fiber
    * @param {*} workInProgress 新fiber h1
    */
    function updateHostComponent(current, workInProgress) {
    const { type } = workInProgress
    const nextProps = workInProgress.pendingProps
    let nextChildren = nextProps.children
    //判断当前虚拟DOM它的儿子是不是一个文本独生子
    const isDirectTextChild = shouldSetTextContent(type, nextProps)
    if (isDirectTextChild) {
    nextChildren = null
    }
    reconcileChildren(current, workInProgress, nextChildren)
    return workInProgress.child
    }
    1. 并发构建 fiber 树

    2. 创建一个 root.current 对应的 workInProgress

      workInProgress = createWorkInProgress(root.current, null)

    1. 递归构建子节点 workLoopSync() 执行工作单元 一个 fiber 就是一个工作单元

      function workLoopSync() {
      while (workInProgress !== null) {
      performUnitOfWork(workInProgress)
      }
      }
    2. reconcileChildren 协调子节点 diff 算法,根据 老 fiber 和新的虚拟 dom 协调,尽可能服用老 fiber,

    3. 根据虚拟 DOM 创建新的 Fiber 节点,并且让新创建的 fiber 的 return 指向父 fiber,给 fiber 上面添加副作用 Placement

    4. 返回新创建的第一个子 fiber,赋给 workInProgress,递归执行

    5. 没有子 fiber 执行该 fiber 的完成阶段任务

  5. completeUnitOfWork(unitOfWork)完成工作单元

    执行一个 fiber 的完成工作,如果是原生组件的话就是创建真实的 DOM 节点

    给 fiber.stateNode, fiber.fibersubtreeFlags fiber.flags 赋值 标记副作用

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

    /**
    * 完成一个fiber节点
    * @param {*} current 老fiber
    * @param {*} workInProgress 新的构建的fiber
    */
    export function completeWork(current, workInProgress) {
    const newProps = workInProgress.pendingProps
    switch (workInProgress.tag) {
    case HostRoot:
    bubbleProperties(workInProgress)
    break
    //如果完成的是原生节点的话
    case HostComponent:
    ///现在只是在处理创建或者说挂载新节点的逻辑,后面此处分进行区分是初次挂载还是更新
    //创建真实的DOM节点
    const { type } = workInProgress
    //如果老fiber存在,并且老fiber上真实DOM节点,要走节点更新的逻辑
    if (current !== null && workInProgress.stateNode !== null) {
    updateHostComponent(current, workInProgress, type, newProps)
    if ((current.ref !== workInProgress.ref) !== null) {
    markRef(workInProgress)
    }
    } else {
    const instance = createInstance(type, newProps, workInProgress)
    //把自己所有的儿子都添加到自己的身上
    appendAllChildren(instance, workInProgress)
    workInProgress.stateNode = instance
    finalizeInitialChildren(instance, type, newProps)
    if (workInProgress.ref !== null) {
    markRef(workInProgress)
    }
    }
    bubbleProperties(workInProgress)
    break
    case FunctionComponent:
    bubbleProperties(workInProgress)
    break
    case HostText:
    //如果完成的fiber是文本节点,那就创建真实的文本节点
    const newText = newProps
    //创建真实的DOM节点并传入stateNode
    workInProgress.stateNode = createTextInstance(newText)
    //向上冒泡属性
    bubbleProperties(workInProgress)
    break
    }
    }

    export function createInstance(type, props, internalInstanceHandle) {
    const domElement = document.createElement(type)
    //预先缓存fiber节点到DOM元素上
    precacheFiberNode(internalInstanceHandle, domElement)
    //把属性直接保存在domElement的属性上
    updateFiberProps(domElement, props)

    return domElement
    }

  6. fiber 树都构建完成 进入体检阶段

  7. commitRoot(root) 开始进入提交 阶段,就是执行副作用,修改真实 DOM

    1. 先看有没有需要删除的,递归删除子 fiber ,要执行副作用
    2. 插入或者移动,需要找到最近的不需要移动的真实弟弟,进行插入
/**
* 遍历fiber树,执行fiber上的副作用
* @param {*} finishedWork fiber节点
* @param {*} root 根节点
*/
export function commitMutationEffectsOnFiber(finishedWork, root) {
const current = finishedWork.alternate
const flags = finishedWork.flags
switch (finishedWork.tag) {
case FunctionComponent: {
//先遍历它们的子节点,处理它们的子节点上的副作用
recursivelyTraverseMutationEffects(root, finishedWork)
//再处理自己身上的副作用
commitReconciliationEffects(finishedWork)
if (flags & Update) {
commitHookEffectListUnmount(HookHasEffect | HookLayout, finishedWork)
}
break
}
case HostRoot:
case HostText: {
//先遍历它们的子节点,处理它们的子节点上的副作用
recursivelyTraverseMutationEffects(root, finishedWork)
//再处理自己身上的副作用
commitReconciliationEffects(finishedWork)
break
}
case HostComponent: {
//先遍历它们的子节点,处理它们的子节点上的副作用
recursivelyTraverseMutationEffects(root, finishedWork)
//再处理自己身上的副作用
commitReconciliationEffects(finishedWork)
if (flags & Ref) {
commitAttachRef(finishedWork)
}
//处理DOM更新
if (flags & Update) {
//获取真实DOM
const instance = finishedWork.stateNode
//更新真实DOM
if (instance !== null) {
const newProps = finishedWork.memoizedProps
const oldProps = current !== null ? current.memoizedProps : newProps
const type = finishedWork.type
const updatePayload = finishedWork.updateQueue
finishedWork.updateQueue = null
if (updatePayload) {
commitUpdate(instance, updatePayload, type, oldProps, newProps, finishedWork)
}
}
}
break
}
default:
break
}
}

/**
* 递归遍历处理变更的作用
* @param {*} root 根节点
* @param {*} parentFiber 父fiber
*/
function recursivelyTraverseMutationEffects(root, parentFiber) {
//先把父fiber上该删除的节点都删除
const deletions = parentFiber.deletions
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i]
commitDeletionEffects(root, parentFiber, childToDelete)
}
}
//再去处理剩下的子节点
if (parentFiber.subtreeFlags & MutationMask) {
let { child } = parentFiber
while (child !== null) {
commitMutationEffectsOnFiber(child, root)
child = child.sibling
}
}
}

//处理自己身上的副作用,插入 或者移动
function commitReconciliationEffects(finishedWork) {
const { flags } = finishedWork
//如果此fiber要执行插入操作的话
if (flags & Placement) {
//进行插入操作,也就是把此fiber对应的真实DOM节点添加到父真实DOM节点上
commitPlacement(finishedWork)
//把flags里的Placement删除
finishedWork.flags & ~Placement
}
}

/**
* 把此fiber的真实DOM插入到父DOM里
* @param {*} finishedWork
*/
function commitPlacement(finishedWork) {
const parentFiber = getHostParentFiber(finishedWork)
switch (parentFiber.tag) {
case HostRoot: {
const parent = parentFiber.stateNode.containerInfo
const before = getHostSibling(finishedWork) //获取最近的弟弟真实DOM节点
insertOrAppendPlacementNode(finishedWork, before, parent)
break
}
case HostComponent: {
const parent = parentFiber.stateNode
const before = getHostSibling(finishedWork)
insertOrAppendPlacementNode(finishedWork, before, parent)
break
}
default:
break
}
}

总结

  • jsx 转换,上次虚拟 dom
  • 创建跟 fiber
  • 开启工作循环,执行工作单元 构建 fiber 树,给 fiber 增加 flag
  • 完成工作单元的构建,创建真实的 dom,更新属性,此时没有儿子
  • 提交阶段 ,执行副作用,修改真实 DOM