【从0实现React18】 (五) 初探react mount流程 完成核心递归流程

更新流程的目的:

  • 生成wip fiberNode树
  • 标记副作用flags

更新流程的步骤:

  • 递:beginWork
  • 归:completeWork

在 上一节 ,我们探讨了 React 应用在首次渲染或后续更新时的整体更新流程。在 Reconciler 工作流程中,beginWorkcompleteWork 两个方法起到了关键作用。beginWork 负责构建表示更新的 Fiber 树,而 completeWork 则将这个 Fiber 树映射到实际的 DOM 结构上。接下来,我们将深入实现这两个方法。

  1. 开发环境打印日志

首先,在开发环境下增加 DEV 标识,以便在开发环境中方便地打印日志:

pnpm i -D -w @rollup/plugin-replace

安装完成后,在 scripts/rollup/utils.js 中引入:

 // scripts/rollup/utils.js  // ...  import replace from  '@rollup/plugin-replace' ;  // ...   export  function  getBaseRollupPlugins ( { alias = { __DEV__: true }, typescript = {} } = {} ) {  return [ replace (alias), cjs (), ts (typescript)]; } 

这样我们就可以在开发环境中打印日志了。

  1. 实现 beginWork

对于如下结构的reactElement:

<A><B/>
<A/>

当进入A的beginWork时,通过对比B的current fiberNode 与B的reactElement,生成对应wip fiberNode


beginWork 函数在向下遍历阶段执行,根据 Fiber 节点的类型(HostRootHostComponentHostText)分发任务给不同的处理函数,处理具体节点类型的更新逻辑:

export const beginWork = (wip: FiberNode) => {switch (wip.tag) {case HostRoot:return updateHostRoot(wip)case HostComponent:return updateHostComponent(wip)case HostText:return nulldefault:if (__DEV__) {console.warn('beginWork为实现的类型', wip)}break}
}
  1. HostRoot

    /** 处理根节点的更新,包括协调处理根节点的属性 以及子节点的更新逻辑 */
    function updateHostRoot(wip: FiberNode) {const baseState = wip.memoizedStateconst updateQueue = wip.updateQueue as UpdateQueue<Element>const pending = updateQueue.shared.pendingupdateQueue.shared.pending = null // 清空更新链表// 1.计算状态的最新值const { memoizedState } = processUpdateQueue(baseState, pending) // 计算待更新状态的最新值wip.memoizedState = memoizedState // 更新协调后的状态最新值// 2. 创造子fiberNodeconst nextChildren = wip.memoizedState // 获取 children 属性reconcileChildren(wip, nextChildren) // 处理根节点的子节点,可能会递归调用其他协调函数;// 返回经过协调后的新的子节点链表return wip.child
    }
    
    • 表示根节点,即应用的最顶层节点;
    • 调用 updateHostRoot 函数,处理根节点的更新,包括协调处理根节点的属性以及子节点的更新逻辑;
    • 调用 reconcileChildren 函数,处理根节点的子节点,可能会递归调用其他协调函数;
    • 返回 workInProgress.child 表示经过协调后的新的子节点链表;
  1. HostComponent

    function updateHostComponent(wip: FiberNode) {const nextProps = wip.pendingProps//  创造子fiberNodeconst nextChildren = nextProps.children // 获取Dom的children属性reconcileChildren(wip, nextChildren) // 处理原生 DOM 元素的子节点更新,可能会递归调用其他协调函数;return wip.child
    }
    
    • 表示原生 DOM 元素节点,例如 <div><span> 等;
    • 调用 updateHostComponent 函数,处理原生 DOM 元素节点的更新,负责协调处理属性和子节点的更新逻辑;
    • 调用 reconcileChildren 函数,处理原生 DOM 元素的子节点更新;
    • 返回 workInProgress.child 表示经过协调后的新的子节点链表;
  1. HostText

    • 表示文本节点,即 DOM 中的文本内容,例如 <p>123</p> 中的 123
    • 调用 updateHostText 函数,协调处理文本节点的内容更新;
    • 返回 null 表示已经是叶子节点,没有子节点了;

其中 reconcileChildren 函数的作用是,通过对比子节点的 current FiberNode 与 子节点的 ReactElement,来生成子节点对应的 workInProgress FiberNode。(current 是与视图中真实 UI 对应的 Fiber 树,workInProgress 是触发更新后正在 Reconciler 中计算的 Fiber 树。)

function reconcileChildren(wip: FiberNode, children?: ReactElementType) {const current = wip.alternateif (current !== null) {// updatewip.child = reconcileChildFibers(wip, current?.child, children)} else {// mountwip.child = mountChildFibers(wip, null, children)}
}

reconcileChildren 函数中调用了 reconcileChildFibersmountChildFibers 两个函数,它们分别负责处理更新阶段和首次渲染阶段的子节点协调。

reconcileChildFibers:

  • reconcileChildFibers 函数作用于组件的更新阶段,即当组件已经存在于 DOM 中,需要进行更新时。
  • 主要任务是协调处理当前组件实例的子节点,对比之前的子节点(current)和新的子节点(workInProgress)之间的变化。
  • 根据子节点的类型和 key 进行对比,决定是复用、更新、插入还是删除子节点,最终形成新的子节点链表。

mountChildFibers:

  • mountChildFibers 函数作用于组件的首次渲染阶段,即当组件第一次被渲染到 DOM 中时。
  • 主要任务是协调处理首次渲染时组件实例的子节点。
  • 但此时是首次渲染,没有之前的子节点,所以主要是创建新的子节点链表。
 // packages/react-reconciler/src/childFiber.ts
/** 实现生成子节点fiber 以及标记Flags的过程 */import { ReactElementType } from 'shared/ReactTypes'
import { FiberNode, createFiberFromElement } from './fiber'
import { REACT_ELEMENT_TYPE } from 'shared/ReactSymbols'
import { HostText } from './workTags'
import { Placement } from './fiberFlags'function ChildrenReconciler(shouldTrackEffects: boolean) {/** 处理单个 Element 节点的情况对比 currentFiber 与 ReactElement生成 workInProgress FiberNode */function reconcileSingleElement(returnFiber: FiberNode,currentFiber: FiberNode | null,element: ReactElementType) {// 根据element创建fiberconst fiber = createFiberFromElement(element)fiber.return = returnFiberreturn fiber}/** 处理文本节点的情况对比 currentFiber 与 ReactElement生成 workInProgress FiberNode */function reconcileSingleTextNode(returnFiber: FiberNode,currentFiber: FiberNode | null,content: string | number) {const fiber = new FiberNode(HostText, { content }, null)fiber.return = returnFiberreturn fiber}/** 为 Fiber 节点添加更新 flags */function placeSingleChild(fiber: FiberNode) {// 优化策略,首屏渲染且追踪副作用时,才添加更新 flagsif (shouldTrackEffects && fiber.alternate === null) {fiber.flags |= Placement}return fiber}// 闭包,根据 shouldTrackSideEffects 返回不同 reconcileChildFibers 的实现return function reconcileChildFibers(returnFiber: FiberNode,currentFiber: FiberNode | null,newChild?: ReactElementType) {// 判断当前fiber的类型// 1. 单个 Element 节点if (typeof newChild === 'object' && newChild !== null) {switch (newChild.$$typeof) {case REACT_ELEMENT_TYPE:return placeSingleChild(reconcileSingleElement(returnFiber, currentFiber, newChild))default:if (__DEV__) {console.warn('未实现的reconcile类型', newChild)}break}}// TODO 2. 多个 Element 节点 ul > li*3// 3. HostText 节点if (typeof newChild === 'string' || typeof newChild === 'number') {return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFiber, newChild))}// 4. 其他if (__DEV__) {console.warn('未实现的reconcile类型', newChild)}return null}
}/** 处理更新阶段的子节点协调,组件的更新阶段中,追踪副作用*/
export const reconcileChildFibers = ChildrenReconciler(true)
/** 处理首次渲染阶段的子节点协调,首屏渲染阶段中不追踪副作用,只对根节点执行一次 DOM 插入操作*/
export const mountChildFibers = ChildrenReconciler(false)
// packages/react-reconciler/src/fiber.ts
// .../** 根据element创建fiber并返回 */
export function createFiberFromElement(element: ReactElementType) {const { type, key, props } = elementlet fiberTag: WorkTag = FunctionComponentif (typeof type === 'string') {// <div/>  type: 'div'fiberTag = HostComponent} else if (typeof type !== 'function' && __DEV__) {console.warn('未定义的type类型', element)}// 创建 fiber 节点const fiber = new FiberNode(fiberTag, props, key)fiber.type = typereturn fiber
}

beginWork性能优化策略

考虑如下结构的reacetElement:

<div><p>练习时长</p><span>两年半</span>
</div>

理论上mount流程完毕后包含的flags:

  • 两年半 Placement
  • Span Placement
  • 练习时长 Placement
  • P Placement
  • Div Placement

相比执行5次Placement,我们可以构建好离屏DOM后,对div执行1次Placement操作

  1. 实现 completeWork

需要解决的问题:

  • 对于 Host 类型 fiberNode: 构建离屏Dom树
  • 标记 Update flag (TODO)

completeWork 函数在向上遍历阶段执行,根据 Fiber 节点的类型(HostRootHostComponentHostText 等)构建 DOM 节点,收集更新 flags,并根据更新 flags 执行不同的 DOM 操作:

  • HostComponent:

    • 表示原生 DOM 元素节点;
    • 构建 DOM 节点,并调用 appendAllChildren 函数将 DOM 插入到 DOM 树中;
    • 收集更新 flags,并根据更新 flags 执行不同的 DOM 操作,例如插入新节点、更新节点属性、删除节点等;
  • HostText:

    • 表示文本节点;
    • 构建 DOM 节点,并将 DOM 插入到 DOM 树中;
    • 收集更新 flags,根据 flags 的值,更新文本节点的内容;
  • HostRoot:

    • 表示根节点;
    • 会执行一些与根节点相关的最终操作,例如处理根节点的属性,确保整个应用更新完毕;
export const completeWork = (wip: FiberNode) => {const newProps = wip.pendingPropsconst current = wip.alternateswitch (wip.tag) {case HostComponent:if (current !== null && wip.stateNode) {//update} else {// mount  构建离屏的 Dom 树// 1. 构建 Domconst instance = createInstance(wip.type, newProps)// 2. 将Dom插入到Dom树中appendAllChildren(instance, wip)wip.stateNode = instance}bubbleProperties(wip)return nullcase HostText:if (current !== null && wip.stateNode) {//update} else {// mount// 1. 构建 Domconst instance = createTextInstance(newProps.content)wip.stateNode = instance}bubbleProperties(wip)return nullcase HostRoot:bubbleProperties(wip)return nulldefault:if (__DEV__) {console.warn('completeWork未实现的类型', wip)}break}
}

其中,appendAllChildren 函数负责递归地将组件的子节点添加到指定的 parent 中,它通过深度优先遍历 workInProgress 的子节点链表,处理每个子节点的类型。先处理当前节点的所有子节点,再处理兄弟节点。

如果它是原生 DOM 元素节点或文本节点,则将其添加到父节点中;如果是其他类型的组件节点并且有子节点,则递归处理其子节点。

 // packages/react-reconciler/src/completeWork.ts
// ...
function appendAllChildren(parent: FiberNode, wip: FiberNode) {let node = wip.child// 递归插入while (node !== null) {if (node?.tag === HostComponent || node?.tag === HostText) {appendInitialChild(parent, node?.stateNode)} else if (node.child !== null) {node.child.return = nodenode = node.childcontinue}// 终止条件if (node === wip) {return}// 子节点结束,开始处理兄弟节点while (node.sibling === null) {// 1.当前节点无兄弟节点if (node.return === null || node.return === wip) {return}node = node?.return}// 2.当前节点有兄弟节点node.sibling.return = node.returnnode = node.sibling}
}

completeWork 性能优化策略

flags分布在不同fiberNode中,如何快速他们?

  • 利用completeWork向上遍历(归)的流程,将子fiberNode的flags冒泡到父fiberNode

创建 bubbleProperties 函,负责在 completeWork 函数向上遍历的过程中,通过向上冒泡子节点的 flags,将所有更新 flags 收集到根节点。主要包含以下步骤:

  • 从当前需要冒泡属性的 Fiber 节点开始,检查是否有需要冒泡的属性。
  • 如果当前节点有需要冒泡的属性,将这些属性冒泡到父节点的 subtreeFlags 或其他适当的属性中。
  • 递归调用 bubbleProperties 函数,处理父节点,将属性继续冒泡到更上层的祖先节点,直至达到根节点。
 // packages/react-reconciler/src/completeWork.ts
// .../** 收集更新 flags,将子 FiberNode 的 flags 冒泡到父 FiberNode 上 */
function bubbleProperties(wip: FiberNode) {let subtreeFlags = NoFlagslet child = wip.childwhile (child !== null) {subtreeFlags |= child.subtreeFlagssubtreeFlags |= child.flagschild.return = wipchild = child.sibling}wip.subtreeFlags |= subtreeFlags
}
  1. 位运算简介

flags 是 React 中很重要的一环,具体作用是通过二进制在每个 Fiber 节点保存其本身与子节点的 flags。在保存与处理 flags 时,使用了一些二进制运算符,我们来复习一下:

1. | 运算

| 运算的两个位都为 0 时,结果才为 0:

  • 1 | 1 = 1
  • 1 | 0 = 1
  • 0 | 0 = 0

React 利用了 | 运算符的特性来存储 flags,如:

ts
复制代码
const NoFlags = /*            */ 0b0000000;const PerformedWork = /*      */ 0b0000001;const Placement = /*          */ 0b0000010;const Update = /*             */ 0b0000100;const ChildDeletion = /*      */ 0b0001000;const flags = Placement | Update; //此时 flags = 0b0000110

2. & 运算

& 运算的两个位都为 1 时,结果才为 1:

  • 1 & 1 = 1
  • 1 & 0 = 0
  • 0 & 0 = 0

React 中会用一个 flags & 某一个 flag,来判断 flags 中是否包含某一个 flag,如:

ts
复制代码
const flags = Placement | Update; //此时 flags = 0b0000110Boolean(flags & Placement); // true, 说明 flags 中包含 PlacementBoolean(flags & ChildDeletion); // false, 说明 flags 中不包含 ChildDeletion

3. ~ 运算

运算符会把每一位取反,0 变 1,1 变 0:

  • ~1 = 0
  • ~0 = 1

在 React 中,~ 运算符同样是常用操作,如:

ts
复制代码
let flags = Placement | Update; //此时 flags = 0b0000110flags &= ~Placement; //此时 flags = 0b0000100

通过 ~ 运算符与 & 运算符的结合,从 flags 中删除了 Placement 这个 flag。


至此,我们就实现了 React 协调阶段中的 beginWorkcompleteWork 函数,生成了一棵表示更新的 Fiber 树,并收集了树中节点的更新 flags,下一节我们将根据这些 flags 执行对应的 DOM 操作。

借鉴链接: https://juejin.cn/post/7347911786802511924
更新流程的目的:

  • 生成wip fiberNode树
  • 标记副作用flags

更新流程的步骤:

  • 递:beginWork
  • 归:completeWork

在 上一节 ,我们探讨了 React 应用在首次渲染或后续更新时的整体更新流程。在 Reconciler 工作流程中,beginWorkcompleteWork 两个方法起到了关键作用。beginWork 负责构建表示更新的 Fiber 树,而 completeWork 则将这个 Fiber 树映射到实际的 DOM 结构上。接下来,我们将深入实现这两个方法。

  1. 开发环境打印日志

首先,在开发环境下增加 DEV 标识,以便在开发环境中方便地打印日志:

pnpm i -D -w @rollup/plugin-replace

安装完成后,在 scripts/rollup/utils.js 中引入:

 // scripts/rollup/utils.js  // ...  import replace from  '@rollup/plugin-replace' ;  // ...   export  function  getBaseRollupPlugins ( { alias = { __DEV__: true }, typescript = {} } = {} ) {  return [ replace (alias), cjs (), ts (typescript)]; } 

这样我们就可以在开发环境中打印日志了。

  1. 实现 beginWork

对于如下结构的reactElement:

<A><B/>
<A/>

当进入A的beginWork时,通过对比B的current fiberNode 与B的reactElement,生成对应wip fiberNode


beginWork 函数在向下遍历阶段执行,根据 Fiber 节点的类型(HostRootHostComponentHostText)分发任务给不同的处理函数,处理具体节点类型的更新逻辑:

export const beginWork = (wip: FiberNode) => {switch (wip.tag) {case HostRoot:return updateHostRoot(wip)case HostComponent:return updateHostComponent(wip)case HostText:return nulldefault:if (__DEV__) {console.warn('beginWork为实现的类型', wip)}break}
}
  1. HostRoot

    /** 处理根节点的更新,包括协调处理根节点的属性 以及子节点的更新逻辑 */
    function updateHostRoot(wip: FiberNode) {const baseState = wip.memoizedStateconst updateQueue = wip.updateQueue as UpdateQueue<Element>const pending = updateQueue.shared.pendingupdateQueue.shared.pending = null // 清空更新链表// 1.计算状态的最新值const { memoizedState } = processUpdateQueue(baseState, pending) // 计算待更新状态的最新值wip.memoizedState = memoizedState // 更新协调后的状态最新值// 2. 创造子fiberNodeconst nextChildren = wip.memoizedState // 获取 children 属性reconcileChildren(wip, nextChildren) // 处理根节点的子节点,可能会递归调用其他协调函数;// 返回经过协调后的新的子节点链表return wip.child
    }
    
    • 表示根节点,即应用的最顶层节点;
    • 调用 updateHostRoot 函数,处理根节点的更新,包括协调处理根节点的属性以及子节点的更新逻辑;
    • 调用 reconcileChildren 函数,处理根节点的子节点,可能会递归调用其他协调函数;
    • 返回 workInProgress.child 表示经过协调后的新的子节点链表;
  1. HostComponent

    function updateHostComponent(wip: FiberNode) {const nextProps = wip.pendingProps//  创造子fiberNodeconst nextChildren = nextProps.children // 获取Dom的children属性reconcileChildren(wip, nextChildren) // 处理原生 DOM 元素的子节点更新,可能会递归调用其他协调函数;return wip.child
    }
    
    • 表示原生 DOM 元素节点,例如 <div><span> 等;
    • 调用 updateHostComponent 函数,处理原生 DOM 元素节点的更新,负责协调处理属性和子节点的更新逻辑;
    • 调用 reconcileChildren 函数,处理原生 DOM 元素的子节点更新;
    • 返回 workInProgress.child 表示经过协调后的新的子节点链表;
  1. HostText

    • 表示文本节点,即 DOM 中的文本内容,例如 <p>123</p> 中的 123
    • 调用 updateHostText 函数,协调处理文本节点的内容更新;
    • 返回 null 表示已经是叶子节点,没有子节点了;

其中 reconcileChildren 函数的作用是,通过对比子节点的 current FiberNode 与 子节点的 ReactElement,来生成子节点对应的 workInProgress FiberNode。(current 是与视图中真实 UI 对应的 Fiber 树,workInProgress 是触发更新后正在 Reconciler 中计算的 Fiber 树。)

function reconcileChildren(wip: FiberNode, children?: ReactElementType) {const current = wip.alternateif (current !== null) {// updatewip.child = reconcileChildFibers(wip, current?.child, children)} else {// mountwip.child = mountChildFibers(wip, null, children)}
}

reconcileChildren 函数中调用了 reconcileChildFibersmountChildFibers 两个函数,它们分别负责处理更新阶段和首次渲染阶段的子节点协调。

reconcileChildFibers:

  • reconcileChildFibers 函数作用于组件的更新阶段,即当组件已经存在于 DOM 中,需要进行更新时。
  • 主要任务是协调处理当前组件实例的子节点,对比之前的子节点(current)和新的子节点(workInProgress)之间的变化。
  • 根据子节点的类型和 key 进行对比,决定是复用、更新、插入还是删除子节点,最终形成新的子节点链表。

mountChildFibers:

  • mountChildFibers 函数作用于组件的首次渲染阶段,即当组件第一次被渲染到 DOM 中时。
  • 主要任务是协调处理首次渲染时组件实例的子节点。
  • 但此时是首次渲染,没有之前的子节点,所以主要是创建新的子节点链表。
 // packages/react-reconciler/src/childFiber.ts
/** 实现生成子节点fiber 以及标记Flags的过程 */import { ReactElementType } from 'shared/ReactTypes'
import { FiberNode, createFiberFromElement } from './fiber'
import { REACT_ELEMENT_TYPE } from 'shared/ReactSymbols'
import { HostText } from './workTags'
import { Placement } from './fiberFlags'function ChildrenReconciler(shouldTrackEffects: boolean) {/** 处理单个 Element 节点的情况对比 currentFiber 与 ReactElement生成 workInProgress FiberNode */function reconcileSingleElement(returnFiber: FiberNode,currentFiber: FiberNode | null,element: ReactElementType) {// 根据element创建fiberconst fiber = createFiberFromElement(element)fiber.return = returnFiberreturn fiber}/** 处理文本节点的情况对比 currentFiber 与 ReactElement生成 workInProgress FiberNode */function reconcileSingleTextNode(returnFiber: FiberNode,currentFiber: FiberNode | null,content: string | number) {const fiber = new FiberNode(HostText, { content }, null)fiber.return = returnFiberreturn fiber}/** 为 Fiber 节点添加更新 flags */function placeSingleChild(fiber: FiberNode) {// 优化策略,首屏渲染且追踪副作用时,才添加更新 flagsif (shouldTrackEffects && fiber.alternate === null) {fiber.flags |= Placement}return fiber}// 闭包,根据 shouldTrackSideEffects 返回不同 reconcileChildFibers 的实现return function reconcileChildFibers(returnFiber: FiberNode,currentFiber: FiberNode | null,newChild?: ReactElementType) {// 判断当前fiber的类型// 1. 单个 Element 节点if (typeof newChild === 'object' && newChild !== null) {switch (newChild.$$typeof) {case REACT_ELEMENT_TYPE:return placeSingleChild(reconcileSingleElement(returnFiber, currentFiber, newChild))default:if (__DEV__) {console.warn('未实现的reconcile类型', newChild)}break}}// TODO 2. 多个 Element 节点 ul > li*3// 3. HostText 节点if (typeof newChild === 'string' || typeof newChild === 'number') {return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFiber, newChild))}// 4. 其他if (__DEV__) {console.warn('未实现的reconcile类型', newChild)}return null}
}/** 处理更新阶段的子节点协调,组件的更新阶段中,追踪副作用*/
export const reconcileChildFibers = ChildrenReconciler(true)
/** 处理首次渲染阶段的子节点协调,首屏渲染阶段中不追踪副作用,只对根节点执行一次 DOM 插入操作*/
export const mountChildFibers = ChildrenReconciler(false)
// packages/react-reconciler/src/fiber.ts
// .../** 根据element创建fiber并返回 */
export function createFiberFromElement(element: ReactElementType) {const { type, key, props } = elementlet fiberTag: WorkTag = FunctionComponentif (typeof type === 'string') {// <div/>  type: 'div'fiberTag = HostComponent} else if (typeof type !== 'function' && __DEV__) {console.warn('未定义的type类型', element)}// 创建 fiber 节点const fiber = new FiberNode(fiberTag, props, key)fiber.type = typereturn fiber
}

beginWork性能优化策略

考虑如下结构的reacetElement:

<div><p>练习时长</p><span>两年半</span>
</div>

理论上mount流程完毕后包含的flags:

  • 两年半 Placement
  • Span Placement
  • 练习时长 Placement
  • P Placement
  • Div Placement

相比执行5次Placement,我们可以构建好离屏DOM后,对div执行1次Placement操作

  1. 实现 completeWork

需要解决的问题:

  • 对于 Host 类型 fiberNode: 构建离屏Dom树
  • 标记 Update flag (TODO)

completeWork 函数在向上遍历阶段执行,根据 Fiber 节点的类型(HostRootHostComponentHostText 等)构建 DOM 节点,收集更新 flags,并根据更新 flags 执行不同的 DOM 操作:

  • HostComponent:

    • 表示原生 DOM 元素节点;
    • 构建 DOM 节点,并调用 appendAllChildren 函数将 DOM 插入到 DOM 树中;
    • 收集更新 flags,并根据更新 flags 执行不同的 DOM 操作,例如插入新节点、更新节点属性、删除节点等;
  • HostText:

    • 表示文本节点;
    • 构建 DOM 节点,并将 DOM 插入到 DOM 树中;
    • 收集更新 flags,根据 flags 的值,更新文本节点的内容;
  • HostRoot:

    • 表示根节点;
    • 会执行一些与根节点相关的最终操作,例如处理根节点的属性,确保整个应用更新完毕;
export const completeWork = (wip: FiberNode) => {const newProps = wip.pendingPropsconst current = wip.alternateswitch (wip.tag) {case HostComponent:if (current !== null && wip.stateNode) {//update} else {// mount  构建离屏的 Dom 树// 1. 构建 Domconst instance = createInstance(wip.type, newProps)// 2. 将Dom插入到Dom树中appendAllChildren(instance, wip)wip.stateNode = instance}bubbleProperties(wip)return nullcase HostText:if (current !== null && wip.stateNode) {//update} else {// mount// 1. 构建 Domconst instance = createTextInstance(newProps.content)wip.stateNode = instance}bubbleProperties(wip)return nullcase HostRoot:bubbleProperties(wip)return nulldefault:if (__DEV__) {console.warn('completeWork未实现的类型', wip)}break}
}

其中,appendAllChildren 函数负责递归地将组件的子节点添加到指定的 parent 中,它通过深度优先遍历 workInProgress 的子节点链表,处理每个子节点的类型。先处理当前节点的所有子节点,再处理兄弟节点。

如果它是原生 DOM 元素节点或文本节点,则将其添加到父节点中;如果是其他类型的组件节点并且有子节点,则递归处理其子节点。

 // packages/react-reconciler/src/completeWork.ts
// ...
function appendAllChildren(parent: FiberNode, wip: FiberNode) {let node = wip.child// 递归插入while (node !== null) {if (node?.tag === HostComponent || node?.tag === HostText) {appendInitialChild(parent, node?.stateNode)} else if (node.child !== null) {node.child.return = nodenode = node.childcontinue}// 终止条件if (node === wip) {return}// 子节点结束,开始处理兄弟节点while (node.sibling === null) {// 1.当前节点无兄弟节点if (node.return === null || node.return === wip) {return}node = node?.return}// 2.当前节点有兄弟节点node.sibling.return = node.returnnode = node.sibling}
}

completeWork 性能优化策略

flags分布在不同fiberNode中,如何快速他们?

  • 利用completeWork向上遍历(归)的流程,将子fiberNode的flags冒泡到父fiberNode

创建 bubbleProperties 函,负责在 completeWork 函数向上遍历的过程中,通过向上冒泡子节点的 flags,将所有更新 flags 收集到根节点。主要包含以下步骤:

  • 从当前需要冒泡属性的 Fiber 节点开始,检查是否有需要冒泡的属性。
  • 如果当前节点有需要冒泡的属性,将这些属性冒泡到父节点的 subtreeFlags 或其他适当的属性中。
  • 递归调用 bubbleProperties 函数,处理父节点,将属性继续冒泡到更上层的祖先节点,直至达到根节点。
 // packages/react-reconciler/src/completeWork.ts
// .../** 收集更新 flags,将子 FiberNode 的 flags 冒泡到父 FiberNode 上 */
function bubbleProperties(wip: FiberNode) {let subtreeFlags = NoFlagslet child = wip.childwhile (child !== null) {subtreeFlags |= child.subtreeFlagssubtreeFlags |= child.flagschild.return = wipchild = child.sibling}wip.subtreeFlags |= subtreeFlags
}
  1. 位运算简介

flags 是 React 中很重要的一环,具体作用是通过二进制在每个 Fiber 节点保存其本身与子节点的 flags。在保存与处理 flags 时,使用了一些二进制运算符,我们来复习一下:

1. | 运算

| 运算的两个位都为 0 时,结果才为 0:

  • 1 | 1 = 1
  • 1 | 0 = 1
  • 0 | 0 = 0

React 利用了 | 运算符的特性来存储 flags,如:

ts
复制代码
const NoFlags = /*            */ 0b0000000;const PerformedWork = /*      */ 0b0000001;const Placement = /*          */ 0b0000010;const Update = /*             */ 0b0000100;const ChildDeletion = /*      */ 0b0001000;const flags = Placement | Update; //此时 flags = 0b0000110

2. & 运算

& 运算的两个位都为 1 时,结果才为 1:

  • 1 & 1 = 1
  • 1 & 0 = 0
  • 0 & 0 = 0

React 中会用一个 flags & 某一个 flag,来判断 flags 中是否包含某一个 flag,如:

ts
复制代码
const flags = Placement | Update; //此时 flags = 0b0000110Boolean(flags & Placement); // true, 说明 flags 中包含 PlacementBoolean(flags & ChildDeletion); // false, 说明 flags 中不包含 ChildDeletion

3. ~ 运算

运算符会把每一位取反,0 变 1,1 变 0:

  • ~1 = 0
  • ~0 = 1

在 React 中,~ 运算符同样是常用操作,如:

ts
复制代码
let flags = Placement | Update; //此时 flags = 0b0000110flags &= ~Placement; //此时 flags = 0b0000100

通过 ~ 运算符与 & 运算符的结合,从 flags 中删除了 Placement 这个 flag。


至此,我们就实现了 React 协调阶段中的 beginWorkcompleteWork 函数,生成了一棵表示更新的 Fiber 树,并收集了树中节点的更新 flags,下一节我们将根据这些 flags 执行对应的 DOM 操作。

借鉴链接: https://juejin.cn/post/7347911786802511924

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/860051.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LLC开关电源开发:LLC设计参考文档(模态分析)

电源简析和全桥LLC模型分析 1.1模拟电源、开关电源和数字电源简介 1.1.1 模拟电源 模拟电源&#xff1a;即变压器电源&#xff0c;通过铁芯、线圈来实现&#xff0c;线圈的匝数决定了两端的电压比&#xff0c;铁芯的作用是传递变化磁场&#xff0c;&#xff08;我国&#xff09…

摒弃反模式:使用Kotlin委托优化Android BaseActivity

摒弃反模式&#xff1a;使用Kotlin委托优化Android BaseActivity 在Android开发中&#xff0c;许多开发者习惯于创建名为“BaseActivity”或“BaseFragment”的基类&#xff0c;以便在所有Activity或Fragment中共享一些通用行为。这种方法乍一看似乎是个好主意&#xff0c;但实…

零基础女生如何入门人工智能,从哪里下手?学习时间大概要多久?

作为一个理工科早期毕业生&#xff0c;出于近乎本能的敏感&#xff0c;格外关注全网热议的ChatGPT。 本来国内就业环境就不好&#xff0c;各行各业内卷越来越严重&#xff0c;加上人工智能的异军突起&#xff0c;各行各业势必将迎来科技进步跨时代的巨大冲击&#xff0c;在此情…

000005 - HDFS 读写流程

HDFS 读写流程 1 HDFS 写数据流程1.1 HDFS 写数据流程图1.2 HDFS 写数据之网络拓扑 - 节点距离计算1.3 机架感知&#xff08;副本存储节点选择&#xff09; 2 HDFS 读数据流程2.1 HDFS 读数据流程图 3 HDFS 如何做到机架感知 1 HDFS 写数据流程 1.1 HDFS 写数据流程图 &#x…

【代码随想录刷题】day02——977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II

977.有序数组的平方 方法一&#xff1a;暴力法 class Solution { public:vector<int> sortedSquares(vector<int>& nums) {// 暴力法for(int i 0; i < nums.size(); i){nums[i] * nums[i];}sort(nums.begin(), nums.end());return nums;} };方法二&#…

安美数字酒店宽带运营系统——命令执行漏洞(CNVD-2021-37784)

声明&#xff1a;本文档或演示材料仅供教育和教学目的使用&#xff0c;任何个人或组织使用本文档中的信息进行非法活动&#xff0c;均与本文档的作者无关。 文章目录 漏洞描述漏洞复现测试工具 漏洞描述 安美数字酒店宽带运营系统 server_ping.php 存在远程命令执行漏洞&#…

2-15 基于matlab的蚁群,模拟退火,遗传,神经网络,禁忌搜索等智能优化算法对TSP问题

基于matlab的蚁群&#xff0c;模拟退火&#xff0c;遗传&#xff0c;神经网络&#xff0c;禁忌搜索等智能优化算法对TSP问题。五种优化算法对多个城市路径进行规划&#xff0c;通过优化速度、距离可比较五种方法的优劣。程序已调通&#xff0c;可直接运行。 2-15 蚁群优化算法 …

智能屏幕人体雷达感应开关模块,飞睿智能低功耗毫米波雷达技术,让冰箱更节能

在智能家居日益普及的今天&#xff0c;各种创新技术层出不穷&#xff0c;极大地提升了人们的生活品质。其中&#xff0c;人体雷达感应开关模块作为一种创新的传感器技术&#xff0c;正在逐步改变我们对家电设备的传统认知。本文将深入探讨飞睿智能人体雷达感应开关中的毫米波雷…

qml自定义组件

不管是component还是分文件编写的一个自定义组件&#xff0c;遵循如下&#xff1a; a:不管哪一级的子对象 都可以直接调根节点下的方法和属性 b:不管哪一级的子对象 调用非根节点的方法和属性&#xff0c;前面要加上该节点的id,即id.方法()或id.属性的形式 import QtQuick 2.…

使用插件和微调优化 GPT 模型

文章目录 LLM 用例和示例产品警惕 AI 幻觉&#xff1a;限制与考虑使⽤插件和微调优化 GPT 模型 OpenAI 在其网站上展示了许多激励人心的客户故事&#xff0c;我们需要了解这些模型如何改变我们的社会并为商业和创造力开辟新机遇。正如你将看到的&#xff0c;许多企业已经开始使…

JSON.parse(JSON.stringify())导致的响应式属性丢失

console.log("formdata赋值前", this.formdata);console.log("row",row);console.log("row序列化后", JSON.parse(JSON.stringify(row)));this.formdata JSON.parse(JSON.stringify(row)); console.log("formdata赋值后", this.formd…

加密的记事本app哪个好用 记事本哪款好用能上锁

随着科技的进步&#xff0c;越来越多的人开始倾向于使用记事本app来记录生活中的点点滴滴。相较于传统的纸质记事本&#xff0c;这些app不仅记录内容丰富&#xff0c;而且安全性更高。其中&#xff0c;敬业签就是一款备受好评的记事本软件。 敬业签以其强大的功能和出色的安全…

【C++进阶之路】C++11——正则表达式

序言 正则表达式&#xff0c;简而言之就是用来匹配指定模式字符串的工具&#xff0c;在计算机的世界中&#xff0c;它发挥着很大的作用&#xff0c;比如编译器的词法分析&#xff0c;注册时验证密码的复杂度&#xff0c;爬虫爬取固定格式的数据时等场景都要用到。那么本篇的目的…

【SpringCloud-Seata源码分析3】

文章目录 事务的提交客户端提交流程服务端提交流程客户端删除undo_log 事务回滚客户端事务回滚服务端回滚事务 事务的提交 前面两篇我们分析了seata的TC初始化和TM,RM初始化&#xff0c;并且事务准备阶段源码及业务Sql执行&#xff0c;下面我们分析事务的提交源码。 客户端提…

如何应对 Android 面试官 -> MVVM 实战一个新闻客户端 (中)

前言 本章我们基于重构的方式进行一个 MVVM 的实战&#xff0c;我们将一个新闻列表的普通实现&#xff0c;一步一步的改造成 MVVM 的架构模式&#xff0c;一共分为上中下三个章节&#xff0c;本章继续上一章&#xff0c;开始中篇的讲解&#xff1b; 控件化 我们本章向控件化进…

2024年华东杯B题数学建模论文:基于车辆运动学转弯模型的自动驾驶规划问题

摘要 随着自动驾驶技术的发展&#xff0c;车辆转弯问题成为关键挑战。本文针对自动驾驶车辆在转弯过程中的数学建模、路径规划及避障策略进行了深入研究&#xff0c;旨在提升自动驾驶车辆的行驶安全性与效率。 针对问题1&#xff0c;对于四轮前轮驱动车辆的转弯问题&#xff0c…

【C++LeetCode】【热题100】两数相加【中等】-不同效率的题解【1】

题目&#xff1a; 暴力方法&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNode(int x, ListNo…

常见硬件工程师面试题(二)

大家好&#xff0c;我是山羊君Goat。 对于硬件工程师&#xff0c;学习的东西主要和电路硬件相关&#xff0c;所以在硬件工程师的面试中&#xff0c;对于经验是十分看重的&#xff0c;像PCB设计&#xff0c;电路设计原理&#xff0c;模拟电路&#xff0c;数字电路等等相关的知识…

ps基础入门

1.基础 1.1新建文件 1.2创建指定形状 1.4移动工具 1.41移动画布中的任意元素 1.42移动画布 1.43修改画布大小 1.44修改图像大小 1.5框选工具 1.6矩形工具 1.7图层 1.71图层颜色修改 1.72…

Spring事务介绍、Spring集成MyBatis

目录 1.Spring的事务1.1 什么是事务&#xff1f;1.2 事务的特性&#xff08;ACID&#xff09;1.3 Spring 事务实现方式有哪些&#xff1f;1.4 Spring事务管理接口介绍1.4.1 PlatformTransactionManager:事务管理接口1.4.2 TransactionDefinition:事务属性事务管理器接口1.4.3 T…