React 中 root.render 与 unmount 函数的流程

上一篇文章 创建根节点 createRoot 介绍了 createRoot 函数,本文继续说下它的返回值,共三个:

  1. _internalRoot
  2. render
  3. unmount

其中 _internalRoot 在上一篇文章已经介绍,这里重点讲述 renderunmount

render 渲染

js 复制代码
ReactDOMRoot.prototype.render = function(children: ReactNodeList): void {
    const root = this._internalRoot;

    updateContainer(children, root, null, null)
}

unmount 卸载

js 复制代码
ReactDOMRoot.prototype.unmount = function(): void {
  const root = this._internalRoot;

  if(root !== null) {
    this._internalRoot = null;
    const container = root.containerInfo;

    // flushSync 是 React 提供的 API,外部也可以用,参考 https://zh-hans.react.dev/reference/react-dom/flushSync
    flushSync((() => {
      updateContainer(null, root, null, null)
    }))
    unmarkContainerAsRoot(container) // 函数定义在上一篇文章中
  }
}

可以看到,两者都有 updateContainer 函数,下面重点说下。

updateContainer 把子节点渲染到容器内

updateContainer 函数分为 4 步:

js 复制代码
// 把子节点渲染到容器内
function (
  element: ReactNodeList, // 子节点
  container: OpaqueRoot // FiberRoot
): Lane {
  // 1. 获取 current 和 lane
  // 在 React 中,lane 是用于标识 update 优先级,可以理解为表示 update 的优先级的一种机制。每个 update 都会被分配一个或多个 lane,以确定其在更新队列中的优先级顺序。
  const current = container.current // container 对应的 fiber
  // 获取本次 update 对应的 lane
  const lane = requestUpdateLane(current)
  
  // 2. 创建 update
  const update = createUpdate(lane)
  update.payload = {element}
  
  // 3. update 入队,将 update 加入到 fiber 的 updateQueue 中
  const root = enqueueUpdate(current, update, lane);
  if (root !== null) {
    // 4. 调度更新
    scheduleUpdateOnFiber(root, current, lane);
  }
}
requestUpdateLane
js 复制代码
function requestUpdateLane(fiber: Fiber): Lane {
  // React 内部的一些 update,比如 flushSync、setState,会通过上下文变量来跟踪其优先级
  const updateLane: Lane = (getCurrentUpdatePriority(): any);
  if (updateLane !== NoLane) {
    return updateLane
  }

  // React 外部的 update,根据事件类型,向当前环境获取对应的优先级
  const eventLane: Lane = (getCurrentEventPriority(): any);
  return updateLane
}

function getCurrentEventPriority(): EventPriority {
  const currentEvernt = window.event;
  if (currentEvernt === undefined) {
    return DefaultEventPriority
  }

  return getEventPriority(currentEvernt.type)
}

export const DefaultEventPriority: EventPriority = DefaultLane  // 页面初次渲染的 lane 是二进制的 32

let currentUpdatePriority: EventPriority = NoLane

function getCurrentUpdatePriority(): EventPriority {
  return currentUpdatePriority
}

// 点击更新状态时触发
function setCurrentUpdatePriority(newPriority: EventPriority) {
  currentUpdatePriority = newPriority
}
createUpdate

createRoot(root).render() 节点与类组件的 setStateforceUpdate 阶段均会创建 update render() 两次会创建两个 update 类组件中调用两次 setState,也会创建两个 update

js 复制代码
// const UpdateState = 0  // 页面初次渲染,类组件 setState
// const ReplaceState = 1 // 类组件
// const ForceState = 2 // 类组件
// const CaptureState = 3 // 类组件

type Update<State> = {
  lane: Lane,
  tag: 0 | 1 | 2 | 3,
  payload: any, // 携带的参数。初次渲染是子节点;类组件比如 setState 的参数
  callback: (() => nuxed) | null,
  next: Update<State> | null // 单链表
}

const UpdateState = 0
function createUpdate(lane: Lane): Update<mixed> {
  const update: Update<mixed> = {
    lane,
    tag: UpdateState,
    payload: null,
    callback: null,
    next: null
  }

  return update
}
enqueueUpdate

createRoot(root).render() 阶段与类组件的 setStateforceUpdate 阶段最开始调用的是 enqueueUpdate

js 复制代码
type SharedQueue = {
  pending: Update<State> | null, // 单向循环链表,尾节点 -> 头节点
  lanes: Lanes // 多个 update 合并成一个
}

function enqueueUpdate<State>(
  fiber: Fiber,
  update: Update<State>,
  lane: Lane
) {
  const updateQueue = fiber.updateQueue
  if (updateQueue === null) {
    // 只发生在 fiber 卸载时
    return null
  }

  const sharedQueue: SharedQueue<State> = (updateQueue: any).shared

  return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane)
}


type ConcurrentUpdate = {
  next: ConcurrentUpdate,
  lane: Lane
}

type ConcurrentQueue = {
  pending: ConcurrentUpdate | null
}

// 如果渲染正在进行中,并且收到来自并发事件的更新,我们会等到当前的渲染结束(无论是完成还是被中断)之后再将其添加到 fiber 队列中。
// 将其推送到这个数组中,这样我们以后就可以访问 queue、fiber、update 等。
const concurrentQueues: Array<any> = []
let concurrentQueuesIndex = 0;

let concrrentlyUpdatedLanes: Lanes = NoLanes;


function enqueueConcurrentClassUpdate<State>(
  fiber: Fiber,
  queue: ClassQueue<State>,
  update: ClassUpdate<State>,
  lane: Lane
): FiberRoot | null {
  const concurrentQueue: ConcurrentQueue = (queue: any)
  const concurrentUpdate: ConcurrentUpdate = (update: any)
  // 1. update 入队
  enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane)
  // 2. 返回 FiberRoot
  return getRootForUpdateFiber(fiber)
}

function enqueueUpdate(
  fiber: Fiber,
  queue: ConcurrentQueue | null,
  update: ConcurrentUpdate | null,
  lane: Lane
) {
  concurrentQueues[concurrentQueuesIndex++] = fiber
  concurrentQueues[concurrentQueuesIndex++] = queue
  concurrentQueues[concurrentQueuesIndex++] = update
  concurrentQueues[concurrentQueuesIndex++] = lane

  concrrentlyUpdatedLanes = mergeLanes(concrrentlyUpdatedLanes, lane)

  fiber.lanes = mergeLanes(fiber.lanes, lane)
  const alternate = fiber.alternate

  // 如果老节点非空
  if (alternate !== null) {
    alternate.lanes = mergeLanes(alternate.lanes, lane)
  }
}

function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
  return a | b
}
scheduleUpdateOnFiber

页面初次渲染、类组件 setState/forceUpdate、函数组件 useState/useReducer 都会走到更新,都会调用 scheduleUpdateOnFiber 函数。

js 复制代码
function _markRootUpdated(root: FiberRoot, updatedLane: Lane) {
  root.pendingLanes |= updatedLane

  // 如果 update 是 idle 的,将不会处理它,因为我们直到所有常规 update 完成后才会处理 idle 更新
  if(updatedLane !== IdleLane) {
    root.suspendedLanes = NoLandex
    root.pingedLanes = NoLanes
  }
}

// 标记根节点有一个 pending update,即待处理的更新。
function markRootUpdated(root: FiberRoot, updatedLanes: Lanes) {
  _markRootUpdated(root, updatedLanes)

  if (enableInfiniteRenderLoopDetection) {
    // 如果循环超过限制次数(类组件 50 次,函数组件 25 次),抛出错误。比如在类组件的 render 函数里执行 setState
    throwIfInfiniteUpdateLoopDetected()
  }
}

function scheduleUpdateOnFiber(
  root: FiberRoot, // 从根节点开始更新:具体的更新是在子节点上发生的,但是遍历是从根节点开始的
  fiber: Fiber,
  lane: Lane
) {
  markRootUpdated(root, lane)
}
相关推荐
安全系统学习2 分钟前
系统安全之大模型案例分析
前端·安全·web安全·网络安全·xss
涛哥码咖18 分钟前
chrome安装AXURE插件后无效
前端·chrome·axure
OEC小胖胖29 分钟前
告别 undefined is not a function:TypeScript 前端开发优势与实践指南
前端·javascript·typescript·web
行云&流水1 小时前
Vue3 Lifecycle Hooks
前端·javascript·vue.js
Sally璐璐1 小时前
零基础学HTML和CSS:网页设计入门
前端·css
老虎06271 小时前
JavaWeb(苍穹外卖)--学习笔记04(前端:HTML,CSS,JavaScript)
前端·javascript·css·笔记·学习·html
三水气象台1 小时前
用户中心Vue3网页开发(1.0版)
javascript·css·vue.js·typescript·前端框架·html·anti-design-vue
灿灿121381 小时前
CSS 文字浮雕效果:巧用 text-shadow 实现 3D 立体文字
前端·css
烛阴2 小时前
Babel 完全上手指南:从零开始解锁现代 JavaScript 开发的超能力!
前端·javascript
AntBlack2 小时前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python