🍂 React DOM树的构建原理和算法

著有《React 设计原理》《React 用到的一些算法》《javascript地月星》等多个专栏。欢迎关注。

欢迎收看我的专栏,要是有帮助别忘了点赞👍、收藏⭐️、评论📝,你的鼓励是我继续挖干货的动力🔥🔥。

本文为原创内容,商业转载请联系作者获得授权,非商业转载需注明出处,感谢理解~

大家好,我是骑自行车,开奔驰,住别墅的码农。本文介绍遍历Fiber树的过程中,DOM树的创建及其源码appendAllChildren函数。虽然不敢断言这是'第一个'这样拆解的内容,但我相信这里的细节深度,在社区中是少见的。

appendAllChildren

组件节点是一个虚拟节点,没有DOM,那么组件里面的DOM要挂载到哪里?答案是跳过 组件节点挂载。

举例来说,红色的span不会添加到World,而是会被添加到红色的div,红色的div则被添加到div#root。

从左到右,为Fiber树,DOM树。Fiber树上,红色为真实DOM类型Fiber节点(HostComponent),黄色为其他类型Fiber。

源码中是如何进行的呢?

js 复制代码
function appendAllChildren(parent, workInProgress, needsVisibilityToggle, isHidden){
  let node = workInProgress.child;
  while(node!==null){
    //红色节点:只处理真实的节点类型div span p img 纯文本...
    //只有真实DOM节点可以添加到DOM
    if(node.tag === HostComponent || node.tag === HostText){
      appendInitialChild(parent, node.stateNode);//stateNode是dom实例
      
    }else if(node.tag===HostPortal);//啥也没做,用分号";"。Portals (传送门),Modal(弹窗)、Tooltip(工具提示)或 Notifications(通知)这些通常需要脱离父组件的 DOM 堆叠上下文。
    else if(node.child!==null){//黄色节点:除了HostComponent HostText HostPortal外的所有类型
    //Fiber.tag = FunctionComponent,ClassComponent,SuspenseCompent...
    //App、World就是FunctionComponent
      node.child.return = node;
      node = node.child;
      continute;//跳过黄色节点,下钻一层。回到while(node!==null){},子节点继续挂载。
    }
    
    // "自身"检查
    if(node===workInProgress){
      return;
    }

    //流程:当前层有没有兄弟节点?有兄弟节点返回兄弟节点,直到把全部兄弟节点挂载到parent。
   //直到没有兄弟节点后冒泡一层,父节点有没有兄弟节点?把父节点兄弟节点也都挂载到parent。父节点没有兄弟节点,再次冒泡。
   
   
    //第二步:循环冒泡:当前没有兄弟节点,冒泡,更新node为父节点,
    while(node.sibling===null){
      //空值检查 1.冒泡到头部(HostRoot) 2.冒泡到起点(workInProgress)
      if(node.return === null||node.return === workInProgress){
        return;
      }
      //冒泡一层,到父节点
      node=node.return;
    }//结束while

    //第一步:处理完兄弟节点才能「循环冒泡」,更新node为兄弟节点
    node.sibling.return = node
    node = node.sibling; //回到while(node!==null){},兄弟节点继续挂载。
  }//结束while(node!==null){}
}//结束appendAllChildren

function appendInitialChild(parentInstance, child) {
  parentInstance.appendChild(child);
}

//简化的completeWork
function completeWork(current, workInProgress, renderLanes) {
  switch (workInProgress.tag) {
      case HostComponent:{
          var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
          appendAllChildren(instance, workInProgress, false, false);
          workInProgress.stateNode = instance;
      }
   }
}

总流程

起点workInProgress,parent是workInProgress的DOM,node是子节点。

第一个循环是下钻,跳过node为黄色的节点,添加到parent上。

第二个循环是冒泡,如果发生了下砖,要冒泡回到下砖起点,把黄色节点的红色兄弟节点添加到parent。

也就是说会穿透黄色节点 ,一次性把直接它的 红色直接子节点和它的红色兄弟节点都添加到parent上。黄色节点的子节点和兄弟节点被拉到同级DOM了!

要控制好下砖和冒泡之间的层数是相等的,通常只要node.return === workInProgress就是回到原来的层级了。如果是从HostRoot进入,node.return === null,就回到HostRoot了。

js 复制代码
  workInProgress(DOM:parent)
     |       
    父tag=FunctionComponent  ------ 3父2tag=HostComponent ------ 4父3tag=HostComponent
     |下砖、冒泡                  |不会发生下砖、冒泡         |不会发生下砖、冒泡
     1子 ------ 2子                  5子 ------ 6子               7子 ------ 8子

这一次的appendAllChildren只有1 2 3 4被挂载到parent。

5 6 7 8在之前的appendAllChildren已经被添加到3,4了, 因为遍历Fiber树,completeWork的顺序是冒泡的,是从下往上,3,4已经执行过completeWork:appendAllChildren了。

除了跳过黄色节点外,总是在当前节点添加直接下级,不跨级添加DOM。

相关阅读:

ps:谢谢大家点赞支持🫰,希望能积累单篇10个赞收藏。

相关推荐
拂拉氏10 分钟前
【知识讲解】 AVL树从基本成员的介绍到核心接口的实现(插入、判断、删除等等)
数据结构·算法·avl树
YFF菲菲兔1 小时前
React 事件系统:listenToAllSupportedEvents
react.js
可靠的仙人掌1 小时前
SAC(Soft Actor-Critic)算法底座
开发语言·算法·php
海石2 小时前
单调栈复健,顺便,牺牲一下吧,空间复杂度!一切献给AC
算法·leetcode
海石2 小时前
JS击败94%,Hard题想不到动态规划,那就用数组和栈试试
算法·leetcode
多加点辣也没关系4 小时前
JavaScript|第9章:对象 — 基础
开发语言·javascript·ecmascript
alphaTao5 小时前
LeetCode 每日一题 2026/7/6-2026/7/12
算法·leetcode
想吃火锅10055 小时前
【leetcode】56.合并区间js
算法·leetcode·职场和发展
imuliuliang5 小时前
可合并堆在多任务调度中的优势与实现技巧7
算法
学究天人5 小时前
数学公理体系大全:Comprehensive Collection of Mathematical Axiom Systems(卷6)
网络·算法·数学建模·动态规划·几何学·图论·拓扑学