Maison  >  Article  >  interface Web  >  Comment Vue3 restitue-t-il les nœuds virtuels sur la page Web pour le rendu initial ?

Comment Vue3 restitue-t-il les nœuds virtuels sur la page Web pour le rendu initial ?

WBOY
WBOYavant
2023-05-10 15:07:061062parcourir

Texte

La méthode app.mount à l'intérieur de la fonction createApp est un processus de rendu de composant multiplateforme standard : créez d'abord un VNode, puis restituez le VNode.

Comment Vue3 restitue-t-il les nœuds virtuels sur la page Web pour le rendu initial ?

Quand les fonctions virtuelles seront-elles créées et rendues ?

Pendant le processus d'initialisation de vue3, le code source core/packages/runtime-core/src/apiCreateApp.ts pointé par createApp() createApp()指向的源码 core/packages/runtime-core/src/apiCreateApp.ts中

Comment Vue3 restitue-t-il les nœuds virtuels sur la page Web pour le rendu initial ?

export function createAppAPI<HostElement>(
  render: RootRenderFunction<HostElement>,//由之前的baseCreateRenderer中的render传入
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {//rootComponent根组件
    let isMounted = false
    //生成一个具体的对象,提供对应的API和相关属性
    const app: App = (context.app = {//将以下参数传入到context中的app里
      //...省略其他逻辑处理
      //挂载
      mount(
        rootContainer: HostElement,
        isHydrate?: boolean,//是用来判断是否用于服务器渲染,这里不讲所以省略
        isSVG?: boolean
      ): any {
      //如果处于未挂载完毕状态下运行
      if (!isMounted) {
	      //创建一个新的虚拟节点传入根组件和根属性
          const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          )
          // 存储app上下文到根虚拟节点,这将在初始挂载时设置在根实例上。
          vnode.appContext = context
          }
          //渲染虚拟节点,根容器
          render(vnode, rootContainer, isSVG)
          isMounted = true //将状态改变成为已挂载
          app._container = rootContainer
          // for devtools and telemetry
          ;(rootContainer as any).__vue_app__ = app
          return getExposeProxy(vnode.component!) || vnode.component!.proxy
      }},
    })
    return app
  }
}

在mount的过程中,当运行处于未挂载时, const vnode = createVNode(rootComponent as ConcreteComponent,rootProps)创建虚拟节点并且将 vnode(虚拟节点)、rootContainer(根容器),isSVG作为参数传入render函数中去进行渲染。

什么是VNode?

虚拟节点其实就是JavaScript的一个对象,用来描述DOM。

这里可以编写一个实际的简单例子来辅助理解,下面是一段html的普通元素节点

<div class="title" >这是一个标题</div>

如何用虚拟节点来表示?

const VNode ={
	type:&#39;div&#39;,
	props:{
		class:&#39;title&#39;,
		style:{
			fontSize:&#39;16px&#39;,
			width:&#39;100px&#39;
		}
	},
	children:&#39;这是一个标题&#39;,
	key:null
}

这里官方文档给出了建议:完整的 VNode 接口包含其他内部属性,但是强烈建议避免使用这些没有在这里列举出的属性。这样能够避免因内部属性变更而导致的不兼容性问题。

vue3对vnode的type做了更详细的分类。在创建vnode之前先了解一下shapeFlags,这个类对type的类型信息做了对应的编码。以便之后在patch阶段,可以通过不同的类型执行对应的逻辑处理。同时也能看到type有元素,方法函数组件,带状态的组件,子类是文本等。

前置须知

ShapeFlags

// package/shared/src/shapeFlags.ts
//这是一个ts的枚举类,从中也能了解到虚拟节点的类型
export const enum ShapeFlags {
//DOM元素 HTML
  ELEMENT = 1,
  //函数式组件
  FUNCTIONAL_COMPONENT = 1 << 1, //2
  //带状态的组件
  STATEFUL_COMPONENT = 1 << 2,//4
  //子节点是文本
  TEXT_CHILDREN = 1 << 3,//8
  //子节点是数组
  ARRAY_CHILDREN = 1 << 4,//16
  //子节点带有插槽
  SLOTS_CHILDREN = 1 << 5,//32
  //传送,将一个组件内部的模板‘传送&#39;到该组件DOM结构外层中去,例如遮罩层的使用
  TELEPORT = 1 << 6,//64
  //悬念,用于等待异步组件时渲染一些额外的内容,比如骨架屏,不过目前是实验性功能
  SUSPENSE = 1 << 7,//128
  //要缓存的组件
  COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,//256
  //已缓存的组件
  COMPONENT_KEPT_ALIVE = 1 << 9,//512
  //组件
  COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
}//4 | 2

它用来表示当前虚拟节点的类型。我们可以通过对shapeFlag做二进制运算来描述当前节点的本身是什么类型、子节点是什么类型。

为什么要使用Vnode?

因为vnode可以抽象,把渲染的过程抽象化,使组件的抽象能力也得到提升。 然后因为vue需要可以跨平台,讲节点抽象化后可以通过平台自己的实现,使之在各个平台上渲染更容易。 不过同时需要注意的一点,虽然使用的是vnode,但是这并不意味着vnode的性能更具有优势。比如很大的组件,是表格上千行的表格,在render过程中,创建vnode势必得遍历上千次vnode的创建,然后遍历上千次的patch,在更新表格数据中,势必会出现卡顿的情况。即便是在patch中使用diff优化了对DOM操作次数,但是始终需要操作。

Vnode是如何创建的?

vue3 提供了一个 h() 函数用于创建 vnodes:

import {h} from &#39;vue&#39;
h(&#39;div&#39;, { id: &#39;foo&#39; })

其本质也是调用 createVNode()函数。

 const vnode = createVNode(rootComponent as ConcreteComponent,rootProps)

createVNode()位于 core/packages/runtime-core/src/vnode.ts

//创建虚拟节点
export const createVNode = ( _createVNode) as typeof _createVNode
function _createVNode(
//标签类型
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  //数据和vnode的属性
  props: (Data & VNodeProps) | null = null,
  //子节点
  children: unknown = null,
  //patch标记
  patchFlag: number = 0,
  //动态参数
  dynamicProps: string[] | null = null,
  //是否是block节点
  isBlockNode = false
): VNode {

  //内部逻辑处理
  
  //使用更基层的createBaseVNode对各项参数进行处理
  return createBaseVNode(
    type,
    props,
    children,
    patchFlag,
    dynamicProps,
    shapeFlag,
    isBlockNode,
    true
  )
}

刚才省略的内部逻辑处理,这里去除了只有在开发环境下才运行的代码:

先是判断

  if (isVNode(type)) {
	//创建虚拟节点接收到已存在的节点,这种情况发生在诸如 <component :is="vnode"/>
    // #2078 确保在克隆过程中合并refs,而不是覆盖它。
    const cloned = cloneVNode(type, props, true /* mergeRef: true */)
    //如果拥有子节点,将子节点规范化处理
    if (children) {normalizeChildren(cloned, children)}:
	//将拷贝的对象存入currentBlock中
    if (isBlockTreeEnabled > 0 && !isBlockNode && currentBlock) {
      if (cloned.shapeFlag & ShapeFlags.COMPONENT) {
        currentBlock[currentBlock.indexOf(type)] = cloned
      } else {
        currentBlock.push(cloned)
      }
    }
    cloned.patchFlag |= PatchFlags.BAIL
    //返回克隆
    return cloned
  }
  // 类组件规范化
  if (isClassComponent(type)) {
    type = type.__vccOpts 
  }
  // 类(class)和风格(style) 规范化.
  if (props) {
    //对于响应式或者代理的对象,我们需要克隆来处理,以防止触发响应式和代理的变动
    props = guardReactiveProps(props)!
    let { class: klass, style } = props
    if (klass && !isString(klass)) {
      props.class = normalizeClass(klass)
    }
    if (isObject(style)) {
     // 响应式对象需要克隆后再处理,以免触发响应式。
      if (isProxy(style) && !isArray(style)) {
        style = extend({}, style)
      }
      props.style = normalizeStyle(style)
    }
  }

 与之前的shapeFlags枚举类结合,将定好的编码赋值给shapeFlag

  // 将虚拟节点的类型信息编码成一个位图(bitmap)
  // 根据type类型来确定shapeFlag的属性值
  const shapeFlag = isString(type)//是否是字符串
    ? ShapeFlags.ELEMENT//传值1
    : __FEATURE_SUSPENSE__ && isSuspense(type)//是否是悬念类型
    ? ShapeFlags.SUSPENSE//传值128
    : isTeleport(type)//是否是传送类型
    ? ShapeFlags.TELEPORT//传值64
    : isObject(type)//是否是对象类型
    ? ShapeFlags.STATEFUL_COMPONENT//传值4
    : isFunction(type)//是否是方法类型
    ? ShapeFlags.FUNCTIONAL_COMPONENT//传值2
    : 0//都不是以上类型 传值0

以上,将虚拟节点其中一部分的属性处理好之后,再传入创建基础虚拟节点函数中,做更进一步和更详细的属性对象创建。

createBaseVNode 虚拟节点初始化创建

创建基础虚拟节点(JavaScript对象),初始化封装一系列相关的属性。

function createBaseVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,//虚拟节点类型
  props: (Data & VNodeProps) | null = null,//内部的属性
  children: unknown = null,//子节点内容
  patchFlag = 0,//patch标记
  dynamicProps: string[] | null = null,//动态参数内容
  shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,//节点类型的信息编码
  isBlockNode = false,//是否块节点
  needFullChildrenNormalization = false
) {
//声明一个vnode对象,并且将各种属性赋值,从而完成虚拟节点的初始化创建
  const vnode = {
    __v_isVNode: true,//内部属性表示为Vnode
    __v_skip: true,//表示跳过响应式转换
    type, //虚拟节点类型
    props,//虚拟节点内的属性和props
    key: props && normalizeKey(props),//虚拟阶段的key用于diff
    ref: props && normalizeRef(props),//引用
    scopeId: currentScopeId,//作用域id
    slotScopeIds: null,//插槽id
    children,//子节点内容,树形结构
    component: null,//组件
    suspense: null,//传送组件
    ssContent: null,
    ssFallback: null,
    dirs: null,//目录
    transition: null,//内置组件相关字段
    el: null,//vnode实际被转换为dom元素的时候产生的元素,宿主
    anchor: null,//锚点
    target: null,//目标
    targetAnchor: null,//目标锚点
    staticCount: 0,//静态节点数
    shapeFlag,//shape标记
    patchFlag,//patch标记
    dynamicProps,//动态参数
    dynamicChildren: null,//动态子节点
    appContext: null,//app上下文
    ctx: currentRenderingInstance
  } as VNode

  //关于子节点和block节点的标准化和信息编码处理
  return vnode
}

由此可见,创建vnode就是一个对props中的内容进行标准化处理,然后对节点类型进行信息编码,对子节点的标准化处理和类型信息编码,最后创建vnode对象的过程。

render 渲染 VNode

baseCreateRenderer()返回对象中,有render()函数,hydrate用于服务器渲染和createApp函数的。 在baseCreateRenderer()函数中,定义了render()函数,render的内容不复杂。

组件在首次挂载,以及后续的更新等,都会触发mount(),而这些,其实都会调用render()渲染函数。render()会先判断vnode虚拟节点是否存在,如果不存在进行unmount()卸载操作。 如果存在则会调用patch()函数。因此可以推测,patch()的过程中,有关组件相关处理。

Comment Vue3 restitue-t-il les nœuds virtuels sur la page Web pour le rendu initial ?

 const render: RootRenderFunction = (vnode, container, isSVG) => {
    if (vnode == null) {//判断是否传入虚拟节点,如果节点不存在则运行
      if (container._vnode) {//判断容器中是否已有节点
        unmount(container._vnode, null, null, true)//如果已有节点则卸载当前节点
      }
    } else {
    //如果节点存在,则调用patch函数,从参数看,会传入新旧节点和容器
	      patch(container._vnode || null, vnode, container, null, null, null, isSVG)
    }
    flushPreFlushCbs() //组件更新前的回调
    flushPostFlushCbs()//组件更新后的回调
    container._vnode = vnode//将虚拟节点赋值到容器上
  }

patch VNode

这里来看一下有关patch()

Comment Vue3 rend-il les nœuds virtuels au rendu initial de la page Web

// 注意:此闭包中的函数应使用 &#39;const xxx = () => {}&#39;样式,以防止被小写器内联。
// patch:进行diff算法,crateApp->vnode->element
const patch: PatchFn = (
    n1,//老节点
    n2,//新节点
    container,//宿主元素 container
    anchor = null,//锚点,用来标识当我们对新旧节点做增删或移动等操作时,以哪个节点为参照物
    parentComponent = null,//父组件
    parentSuspense = null,//父悬念
    isSVG = false,
    slotScopeIds = null,//插槽
    optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
  ) => {
    if (n1 === n2) {// 如果新老节点相同则停止
      return
    }
    // 打补丁且不是相同类型,则卸载旧节点,锚点后移
    if (n1 && !isSameVNodeType(n1, n2)) {
      anchor = getNextHostNode(n1)
      unmount(n1, parentComponent, parentSuspense, true)
      n1 = null //n1复位
    }
	//是否动态节点优化
    if (n2.patchFlag === PatchFlags.BAIL) {
      optimized = false
      n2.dynamicChildren = null
    }
	//结构n2新节点,获取新节点的类型
    const { type, ref, shapeFlag } = n2
    switch (type) {
      case Text: //文本类
	    processText(n1, n2, container, anchor)//文本节点处理
        break
      case Comment://注释类
        processCommentNode(n1, n2, container, anchor)//处理注释节点
        break
      case Static://静态类
        if (n1 == null) {//如果老节点不存在
          mountStaticNode(n2, container, anchor, isSVG)//挂载静态节点
        }
        break
      case Fragment://片段类
        processFragment(
         //进行片段处理
        )
        break
      default:
        if (shapeFlag & ShapeFlags.ELEMENT) {//如果类型编码是元素
          processElement(
	       n1,
           n2,
           container,
           anchor,
           parentComponent,
           parentSuspense,
           isSVG,
           slotScopeIds,
           optimized
          )
        } else if (shapeFlag & ShapeFlags.COMPONENT) {//如果类型编码是组件
          processComponent(
           n1,
           n2,
           container,
           anchor,
           parentComponent,
           parentSuspense,
           isSVG,
           slotScopeIds,
           optimized
          )
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          ;(type as typeof TeleportImpl).process(
          // 如果类型是传送,进行处理
          )
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
          ;(type as typeof SuspenseImpl).process(
          //悬念处理
          )
        } 
    }
  
    // 设置 参考 ref
    if (ref != null && parentComponent) {
      setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
    }
  }
Comment Vue3 restitue-t-il les nœuds virtuels sur la page Web pour le rendu initial ?En cours de montage, lorsque le l'opération n'est pas montée, const vnode = createVNode(rootComponent as ConcreteComponent,rootProps) crée un nœud virtuel et transmet vnode (nœud virtuel), rootContainer (conteneur racine) et isSVG comme paramètres dans la fonction de rendu pour le rendu.

🎜Qu'est-ce que VNode ? 🎜🎜Le nœud virtuel est en fait un objet JavaScript, utilisé pour décrire le DOM. 🎜🎜Ici, vous pouvez écrire un exemple simple et pratique pour faciliter la compréhension. Ce qui suit est un nœud d'élément commun d'un morceau de HTML🎜
 const processComponent = (
    n1: VNode | null,//老节点
    n2: VNode,//新节点
    container: RendererElement,//宿主
    anchor: RendererNode | null,//锚点
    parentComponent: ComponentInternalInstance | null,//父组件
    parentSuspense: SuspenseBoundary | null,//父悬念
    isSVG: boolean,
    slotScopeIds: string[] | null,//插槽
    optimized: boolean
  ) => {
    n2.slotScopeIds = slotScopeIds
    if (n1 == null) {//如果老节点不存在,初次渲染的时候
	  //省略一部分n2其他情况下的处理
      //挂载组件
        mountComponent(
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
    } else {
    //更新组件
     updateComponent(n1, n2, optimized)
    }
  }
🎜Comment utiliser des nœuds virtuels pour le représenter ? 🎜
  const mountComponent: MountComponentFn = (
    initialVNode,//对应n2 新的节点
    container,//对应宿主
    anchor,//锚点
    parentComponent,//父组件
    parentSuspense,//父传送
    isSVG,//是否SVG
    optimized//是否优化
  ) => {
    // 2.x编译器可以在实际安装前预先创建组件实例。
    const compatMountInstance =
    //判断是不是根组件且是组件
      __COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
    const instance: ComponentInternalInstance =
      compatMountInstance ||
      //创建组件实例
      (initialVNode.component = createComponentInstance(
        initialVNode,
        parentComponent,
        parentSuspense
      ))
    // 如果新节点是缓存组件的话那么将internals赋值给期渲染函数
    if (isKeepAlive(initialVNode)) {
      ;(instance.ctx as KeepAliveContext).renderer = internals
    }
    // 为了设置上下文处理props和slot插槽
    if (!(__COMPAT__ && compatMountInstance)) {
	    //设置组件实例
      setupComponent(instance)
    }
	//setup()是异步的。这个组件在进行之前依赖于异步逻辑的解决
    if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
      parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)
      if (!initialVNode.el) {//如果n2没有宿主
        const placeholder = (instance.subTree = createVNode(Comment))
        processCommentNode(null, placeholder, container!, anchor)
      }
      return
    }
    //设置运行渲染副作用函数
    setupRenderEffect(
      instance,//存储了新节点的组件上下文,props插槽等其他实例属性
      initialVNode,//新节点n2
      container,//容器
      anchor,//锚点
      parentSuspense,//父悬念
      isSVG,//是否SVG
      optimized//是否优化
    )
  }
🎜La documentation officielle ici donne des conseils : L'interface complète VNode contient d'autres propriétés internes, mais il est fortement recommandé d'éviter d'utiliser ces propriétés non répertoriées ici. Cela évite les problèmes d'incompatibilité causés par des modifications de propriétés internes. 🎜🎜vue3 a une classification plus détaillée des types de nœuds virtuels. Avant de créer un vnode, comprenez d'abord shapeFlags. Cette classe encode les informations de type en conséquence. De sorte que dans la phase de patch, le traitement logique correspondant peut être effectué via différents types. Dans le même temps, vous pouvez également voir que le type a des éléments, des composants de fonction de méthode, des composants avec un état, les sous-classes sont du texte, etc. 🎜🎜Pré-requis🎜

ShapeFlags

  const setupRenderEffect: SetupRenderEffectFn = (
    instance,//实例
    initialVNode,//初始化节点
    container,//容器
    anchor,//锚点
    parentSuspense,//父悬念
    isSVG,//是否是SVG
    optimized//优化标记
	  ) => {
  //组件更新方法
    const componentUpdateFn = () => {
	   //如果组件处于未挂载的状态下
      if (!instance.isMounted) {
        let vnodeHook: VNodeHook | null | undefined
        //解构
        const { el, props } = initialVNode
        const { bm, m, parent } = instance
        const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)
        toggleRecurse(instance, false)
        // 挂载前的钩子
        // 挂载前的节点
        toggleRecurse(instance, true)
          //这部分是跟服务器渲染相关的逻辑处理
          //创建子树,同时
        const subTree = (instance.subTree = renderComponentRoot(instance))   
	      //递归
        patch(
            null,//因为是挂载,所以n1这个老节点是空的。
            subTree,//子树赋值到n2这个新节点
            container,//挂载到container上
            anchor,
            instance,
            parentSuspense,
            isSVG
          )
          //保留渲染生成的子树DOM节点
          initialVNode.el = subTree.el
        // 已挂载钩子
        // 挂在后的节点
        //激活为了缓存根的钩子
        // #1742 激活的钩子必须在第一次渲染后被访问 因为该钩子可能会被子类的keep-alive注入。
        instance.isMounted = true
        // #2458: deference mount-only object parameters to prevent memleaks
        // #2458: 遵从只挂载对象的参数以防止内存泄漏
        initialVNode = container = anchor = null as any
      } else {
        // 更新组件
        // 这是由组件自身状态的突变触发的(next: null)。或者父级调用processComponent(下一个:VNode)。
      }
    }
    // 创建用于渲染的响应式副作用
    const effect = (instance.effect = new ReactiveEffect(
      componentUpdateFn,
      () => queueJob(update),
      instance.scope // 在组件的效果范围内跟踪它
    ))
    //更新方法
    const update: SchedulerJob = (instance.update = () => effect.run())
    //实例的uid赋值给更新的id
    update.id = instance.uid
    // 允许递归
    // #1801, #2043 组件渲染效果应允许递归更新
    toggleRecurse(instance, true)
    update() 
  }
🎜Il est utilisé pour indiquer le type du nœud virtuel actuel. Nous pouvons décrire le type du nœud actuel lui-même et le type de ses nœuds enfants en effectuant des opérations binaires sur shapeFlag. 🎜🎜Pourquoi utiliser Vnode ?🎜🎜Parce que vnode peut faire abstraction, abstraire le processus de rendu et améliorer la capacité d'abstraction des composants. Ensuite, comme Vue doit être multiplateforme, l'abstraction de nœud peut être implémentée via la plateforme elle-même, ce qui facilite le rendu sur diverses plateformes. Cependant, il convient de noter que même si vnode est utilisé, cela ne signifie pas que les performances de vnode sont plus avantageuses. Par exemple, pour un gros composant, il s'agit d'une table avec des milliers de lignes. Lors du processus de rendu, la création d'un vnode devra inévitablement parcourir des milliers de créations de vnode, puis parcourir des milliers de correctifs lors de la mise à jour des données de la table, des décalages seront inévitables. se produire. Même si vous utilisez diff dans le patch pour optimiser le nombre d'opérations DOM, les opérations sont toujours nécessaires. 🎜🎜Comment Vnode est-il créé ? 🎜🎜vue3 fournit une fonction h() pour créer des vnodes :🎜
<template>
	<div class="app">
		<p>title</p>
		<helloWorld>
	</div>
</template>
🎜L'essence est également d'appeler la fonction createVNode(). 🎜
<template>
	<div class="hello">
		<p>hello world</p>
	</div>
</template>
🎜createVNode() se trouve dans core/packages/runtime-core/src/vnode.ts🎜
  const processElement = (
    n1: VNode | null, //老节点
    n2: VNode,//新节点
    container: RendererElement,//容器
    anchor: RendererNode | null,//锚点
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
    isSVG = isSVG || (n2.type as string) === &#39;svg&#39;
    if (n1 == null) {//如果没有老节点,其实就是初次渲染,则运行mountElement
      mountElement(
        n2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    } else {
	   //如果是更新节点则运行patchElement
      patchElement(
        n1,
        n2,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    }
  }
🎜Le traitement logique interne vient d'être omis, voici le code qui ne s'exécute que dans l'environnement de développement : 🎜

Premier juge

  const mountElement = (
    vnode: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
    let el: RendererElement
    let vnodeHook: VNodeHook | undefined | null
    const { type, props, shapeFlag, transition, dirs } = vnode
	//创建元素节点
    el = vnode.el = hostCreateElement(
      vnode.type as string,
      isSVG,
      props && props.is,
      props
    )
    // 首先挂载子类,因为某些props依赖于子类内容
    // 已经渲染, 例如 `<select value>`
    // 如果标记判断子节点类型是文本类型
    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
       // 处理子节点是纯文本的情况
      hostSetElementText(el, vnode.children as string)
      //如果标记类型是数组子类
    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
    //挂载子类,进行patch后进行挂载
      mountChildren(
        vnode.children as VNodeArrayChildren,
        el,
        null,
        parentComponent,
        parentSuspense,
        isSVG && type !== &#39;foreignObject&#39;,
        slotScopeIds,
        optimized
      )
    }
    if (dirs) {
      invokeDirectiveHook(vnode, null, parentComponent, &#39;created&#39;)
    }
    // 设置范围id
    setScopeId(el, vnode, vnode.scopeId, slotScopeIds, parentComponent)
    // props相关的处理,比如 class,style,event,key等属性
    if (props) { 
      for (const key in props) { 
        if (key !== &#39;value&#39; && !isReservedProp(key)) {//key值不等于value字符且不是
          hostPatchProp(
            el,
            key,
            null,
            props[key],
            isSVG,
            vnode.children as VNode[],
            parentComponent,
            parentSuspense,
            unmountChildren
          )
        }
      }
      
      if (&#39;value&#39; in props) {
        hostPatchProp(el, &#39;value&#39;, null, props.value)
      }
      if ((vnodeHook = props.onVnodeBeforeMount)) {
        invokeVNodeHook(vnodeHook, parentComponent, vnode)
      }
    }
      Object.defineProperty(el, &#39;__vueParentComponent&#39;, {
        value: parentComponent,
        enumerable: false
      }
    }
    if (dirs) {
      invokeDirectiveHook(vnode, null, parentComponent, &#39;beforeMount&#39;)
    }
    // #1583 对于内部悬念+悬念未解决的情况,进入钩子应该在悬念解决时调用。
    // #1689  对于内部悬念+悬念解决的情况,只需调用它
    const needCallTransitionHooks =
      (!parentSuspense || (parentSuspense && !parentSuspense.pendingBranch)) &&
      transition && !transition.persisted
    if (needCallTransitionHooks) {
      transition!.beforeEnter(el)
    }
	 //把创建的元素el挂载到container容器上。
    hostInsert(el, container, anchor)
    if (
      (vnodeHook = props && props.onVnodeMounted) ||
      needCallTransitionHooks ||
      dirs
    ) {
      queuePostRenderEffect(() => {
        vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
        needCallTransitionHooks && transition!.enter(el)
        dirs && invokeDirectiveHook(vnode, null, parentComponent, &#39;mounted&#39;)
      }, parentSuspense)
    }
  }
const {createElement:hostCreateElement } = options
🎜 Combiné avec la classe d'énumération shapeFlags précédente, attribuez l'encodage déterminé à shapeFlag🎜
createElement: (tag, isSVG, is, props): Element => {
    const el = isSVG
      ? doc.createElementNS(svgNS, tag)
      : doc.createElement(tag, is ? { is } : undefined)
    if (tag === &#39;select&#39; && props && props.multiple != null) {
      ;(el as HTMLSelectElement).setAttribute(&#39;multiple&#39;, props.multiple)
    }
    return el
  },
🎜 Ci-dessus, après avoir traité certains des attributs du nœud virtuel, transmettez-le pour créer la base virtual Dans la fonction de nœud, une création d'objets d'attribut plus approfondie et plus détaillée est effectuée. 🎜

Création d'initialisation de nœud virtuel createBaseVNode

🎜Créez un nœud virtuel de base (objet JavaScript), initialisez et encapsulez une série d'attributs associés. 🎜
const {setElementText: hostSetElementText} = option
setElementText: (el, text) => {
    el.textContent = text
  },
🎜On peut voir que la création d'un vnode est un processus de normalisation du contenu dans les accessoires, puis d'encodage des informations de type de nœud, de normalisation et d'encodage des informations de type des nœuds enfants, et enfin de création de l'objet vnode. 🎜🎜render rend VNode🎜🎜baseCreateRenderer()L'objet renvoyé a la fonction render(), et hydrate est utilisé pour le rendu du serveur et les fonctions createApp. Dans la fonction baseCreateRenderer(), la fonction render() est définie et le contenu du rendu n'est pas compliqué. 🎜🎜Le composant déclenchera mount() lors de son premier montage et lors des mises à jour ultérieures, et celles-ci appelleront en fait la fonction de rendu render(). render() déterminera d'abord si le nœud virtuel vnode existe. S'il n'existe pas, effectuez l'opération de désinstallation unmount(). Si elle existe, la fonction patch() sera appelée. Par conséquent, on peut en déduire que pendant le processus de patch(), les composants pertinents sont traités. 🎜🎜Comment Vue3 rend-il les nœuds virtuels sur la page Web pour le rendu initial 🎜
  const mountChildren: MountChildrenFn = (
    children,//子节点数组里的内容
    container,//容器
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    slotScopeIds,
    optimized,//优化标记
    start = 0
  ) => {
  //遍历子节点中的内容
    for (let i = start; i < children.length; i++) {
    //根据优化标记进行判断进行克隆或者节点初始化处理。
      const child = (children[i] = optimized
        ? cloneIfMounted(children[i] as VNode)
        : normalizeVNode(children[i]))
        //执行patch方法,递归挂载child
      patch(
        null,//因为是初次挂载所以没有老的节点
        child,//虚拟子节点
        container,//容器
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    }
  }
🎜 patch VNode🎜🎜Voici un aperçu du code de la fonction patch(), en se concentrant sur le processus lorsque le composant est rendu pour la première fois. 🎜🎜🎜🎜
// 注意:此闭包中的函数应使用 &#39;const xxx = () => {}&#39;样式,以防止被小写器内联。
// patch:进行diff算法,crateApp->vnode->element
const patch: PatchFn = (
    n1,//老节点
    n2,//新节点
    container,//宿主元素 container
    anchor = null,//锚点,用来标识当我们对新旧节点做增删或移动等操作时,以哪个节点为参照物
    parentComponent = null,//父组件
    parentSuspense = null,//父悬念
    isSVG = false,
    slotScopeIds = null,//插槽
    optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
  ) => {
    if (n1 === n2) {// 如果新老节点相同则停止
      return
    }
    // 打补丁且不是相同类型,则卸载旧节点,锚点后移
    if (n1 && !isSameVNodeType(n1, n2)) {
      anchor = getNextHostNode(n1)
      unmount(n1, parentComponent, parentSuspense, true)
      n1 = null //n1复位
    }
	//是否动态节点优化
    if (n2.patchFlag === PatchFlags.BAIL) {
      optimized = false
      n2.dynamicChildren = null
    }
	//结构n2新节点,获取新节点的类型
    const { type, ref, shapeFlag } = n2
    switch (type) {
      case Text: //文本类
	    processText(n1, n2, container, anchor)//文本节点处理
        break
      case Comment://注释类
        processCommentNode(n1, n2, container, anchor)//处理注释节点
        break
      case Static://静态类
        if (n1 == null) {//如果老节点不存在
          mountStaticNode(n2, container, anchor, isSVG)//挂载静态节点
        }
        break
      case Fragment://片段类
        processFragment(
         //进行片段处理
        )
        break
      default:
        if (shapeFlag & ShapeFlags.ELEMENT) {//如果类型编码是元素
          processElement(
	       n1,
           n2,
           container,
           anchor,
           parentComponent,
           parentSuspense,
           isSVG,
           slotScopeIds,
           optimized
          )
        } else if (shapeFlag & ShapeFlags.COMPONENT) {//如果类型编码是组件
          processComponent(
           n1,
           n2,
           container,
           anchor,
           parentComponent,
           parentSuspense,
           isSVG,
           slotScopeIds,
           optimized
          )
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          ;(type as typeof TeleportImpl).process(
          // 如果类型是传送,进行处理
          )
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
          ;(type as typeof SuspenseImpl).process(
          //悬念处理
          )
        } 
    }
  
    // 设置 参考 ref
    if (ref != null && parentComponent) {
      setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
    }
  }

patch函数可见,主要做的就是 新旧虚拟节点之间的对比,这也是常说的diff算法,结合render(vnode, rootContainer, isSVG)可以看出vnode对应的是n1也就是新节点,而rootContainer对应n2,也就是老节点。其做的逻辑判断是。

  • 新旧节点相同则直接返回

  • 旧节点存在,且新节点和旧节点的类型不同,旧节点将被卸载unmount且复位清空null。锚点移向下个节点。

  • 新节点是否是动态值优化标记

  • 对新节点的类型判断

    • 文本类:processText

    • 注释类:processComment

    • 静态类:mountStaticNode

    • 片段类:processFragment

    • 默认

而这个默认才是主要的部分也是最常用到的部分。里面包含了对类型是元素element、组件component、传送teleport、悬念suspense的处理。这次主要讲的是虚拟节点到组件和普通元素渲染的过程,其他类型的暂时不提,内容展开过于杂乱。

实际上第一次初始运行的时候,patch判断vnode类型根节点,因为vue3书写的时候,都是以组件的形式体现,所以第一次的类型势必是component类型。

Comment Vue3 restitue-t-il les nœuds virtuels sur la page Web pour le rendu initial ?

processComponent 节点类型是组件下的处理

 const processComponent = (
    n1: VNode | null,//老节点
    n2: VNode,//新节点
    container: RendererElement,//宿主
    anchor: RendererNode | null,//锚点
    parentComponent: ComponentInternalInstance | null,//父组件
    parentSuspense: SuspenseBoundary | null,//父悬念
    isSVG: boolean,
    slotScopeIds: string[] | null,//插槽
    optimized: boolean
  ) => {
    n2.slotScopeIds = slotScopeIds
    if (n1 == null) {//如果老节点不存在,初次渲染的时候
	  //省略一部分n2其他情况下的处理
      //挂载组件
        mountComponent(
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
    } else {
    //更新组件
     updateComponent(n1, n2, optimized)
    }
  }

老节点n1不存在null的时候,将挂载n2节点。如果老节点存在的时候,则更新组件。因此mountComponent()最常见的就是在首次渲染的时候,那时旧节点都是空的。

接下来就是看如何挂载组件mountComponent()

  const mountComponent: MountComponentFn = (
    initialVNode,//对应n2 新的节点
    container,//对应宿主
    anchor,//锚点
    parentComponent,//父组件
    parentSuspense,//父传送
    isSVG,//是否SVG
    optimized//是否优化
  ) => {
    // 2.x编译器可以在实际安装前预先创建组件实例。
    const compatMountInstance =
    //判断是不是根组件且是组件
      __COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
    const instance: ComponentInternalInstance =
      compatMountInstance ||
      //创建组件实例
      (initialVNode.component = createComponentInstance(
        initialVNode,
        parentComponent,
        parentSuspense
      ))
    // 如果新节点是缓存组件的话那么将internals赋值给期渲染函数
    if (isKeepAlive(initialVNode)) {
      ;(instance.ctx as KeepAliveContext).renderer = internals
    }
    // 为了设置上下文处理props和slot插槽
    if (!(__COMPAT__ && compatMountInstance)) {
	    //设置组件实例
      setupComponent(instance)
    }
	//setup()是异步的。这个组件在进行之前依赖于异步逻辑的解决
    if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
      parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)
      if (!initialVNode.el) {//如果n2没有宿主
        const placeholder = (instance.subTree = createVNode(Comment))
        processCommentNode(null, placeholder, container!, anchor)
      }
      return
    }
    //设置运行渲染副作用函数
    setupRenderEffect(
      instance,//存储了新节点的组件上下文,props插槽等其他实例属性
      initialVNode,//新节点n2
      container,//容器
      anchor,//锚点
      parentSuspense,//父悬念
      isSVG,//是否SVG
      optimized//是否优化
    )
  }

挂载组件中,除开缓存和悬挂上的函数处理,其逻辑上基本为:创建组件的实例createComponentInstance(),设置组件实例 setupComponent(instance)和设置运行渲染副作用函数setupRenderEffect()

创建组件实例,基本跟创建虚拟节点一样的,内部以对象的方式创建渲染组件实例。 设置组件实例,是将组件中许多数据,赋值给了instance,维护组件上下文,同时对props和插槽等属性初始化处理。

然后是setupRenderEffect 设置渲染副作用函数;

  const setupRenderEffect: SetupRenderEffectFn = (
    instance,//实例
    initialVNode,//初始化节点
    container,//容器
    anchor,//锚点
    parentSuspense,//父悬念
    isSVG,//是否是SVG
    optimized//优化标记
	  ) => {
  //组件更新方法
    const componentUpdateFn = () => {
	   //如果组件处于未挂载的状态下
      if (!instance.isMounted) {
        let vnodeHook: VNodeHook | null | undefined
        //解构
        const { el, props } = initialVNode
        const { bm, m, parent } = instance
        const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)
        toggleRecurse(instance, false)
        // 挂载前的钩子
        // 挂载前的节点
        toggleRecurse(instance, true)
          //这部分是跟服务器渲染相关的逻辑处理
          //创建子树,同时
        const subTree = (instance.subTree = renderComponentRoot(instance))   
	      //递归
        patch(
            null,//因为是挂载,所以n1这个老节点是空的。
            subTree,//子树赋值到n2这个新节点
            container,//挂载到container上
            anchor,
            instance,
            parentSuspense,
            isSVG
          )
          //保留渲染生成的子树DOM节点
          initialVNode.el = subTree.el
        // 已挂载钩子
        // 挂在后的节点
        //激活为了缓存根的钩子
        // #1742 激活的钩子必须在第一次渲染后被访问 因为该钩子可能会被子类的keep-alive注入。
        instance.isMounted = true
        // #2458: deference mount-only object parameters to prevent memleaks
        // #2458: 遵从只挂载对象的参数以防止内存泄漏
        initialVNode = container = anchor = null as any
      } else {
        // 更新组件
        // 这是由组件自身状态的突变触发的(next: null)。或者父级调用processComponent(下一个:VNode)。
      }
    }
    // 创建用于渲染的响应式副作用
    const effect = (instance.effect = new ReactiveEffect(
      componentUpdateFn,
      () => queueJob(update),
      instance.scope // 在组件的效果范围内跟踪它
    ))
    //更新方法
    const update: SchedulerJob = (instance.update = () => effect.run())
    //实例的uid赋值给更新的id
    update.id = instance.uid
    // 允许递归
    // #1801, #2043 组件渲染效果应允许递归更新
    toggleRecurse(instance, true)
    update() 
  }

setupRenderEffect() 最后执行的了 update()方法,其实是运行了effect.run(),并且将其赋值给了instance.updata中。而 effect 涉及到了 vue3 的响应式模块,该模块的主要功能就是,让对象属性具有响应式功能,当其中的属性发生了变动,那effect副作用所包含的函数也会重新执行一遍,从而让界面重新渲染。这一块内容先不管。从effect函数看,明白了调用了componentUpdateFn, 即组件更新方法,这个方法涉及了2个条件,一个是初次运行的挂载,而另一个是节点变动后的更新组件。 componentUpdateFn中进行的初次渲染,主要是生成了subTree然后把subTree传递到patch进行了递归挂载到container上。

subTree是什么?

subTree也是一个vnode对象,然而这里的subTree和initialVNode是不同的。以下面举个例子:

<template>
	<div class="app">
		<p>title</p>
		<helloWorld>
	</div>
</template>

而helloWorld组件中是

标签包含一个

标签

<template>
	<div class="hello">
		<p>hello world</p>
	</div>
</template>

在App组件中, 节点渲染渲染生成的vnode就是 helloWorld组件的initialVNode,而这个组件内部所有的DOM节点就是vnode通过执行renderComponentRoot渲染生成的的subTree。 每个组件渲染的时候都会运行render函数,renderComponentRoot就是去执行render函数创建整个组件内部的vnode,然后进行标准化就得到了该函数的返回结果:子树vnode。 生成子树后,接下来就是继续调用patch函数把子树vnode挂载到container上去。 回到patch后,就会继续对子树vnode进行判断,例如上面的App组件的根节点是

标签,而对应的subTree就是普通元素vnode,接下来就是堆普通Element处理的流程。

当节点的类型是普通元素DOM时候,patch判断运行processElement

Comment Vue3 restitue-t-il les nœuds virtuels sur la page Web pour le rendu initial ?

  const processElement = (
    n1: VNode | null, //老节点
    n2: VNode,//新节点
    container: RendererElement,//容器
    anchor: RendererNode | null,//锚点
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
    isSVG = isSVG || (n2.type as string) === &#39;svg&#39;
    if (n1 == null) {//如果没有老节点,其实就是初次渲染,则运行mountElement
      mountElement(
        n2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    } else {
	   //如果是更新节点则运行patchElement
      patchElement(
        n1,
        n2,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    }
  }

逻辑依旧,如果有n1老节点为null的时候,运行挂载元素的逻辑,否则运行更新元素节点的方法。

以下是mountElement()的代码:

  const mountElement = (
    vnode: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
    let el: RendererElement
    let vnodeHook: VNodeHook | undefined | null
    const { type, props, shapeFlag, transition, dirs } = vnode
	//创建元素节点
    el = vnode.el = hostCreateElement(
      vnode.type as string,
      isSVG,
      props && props.is,
      props
    )
    // 首先挂载子类,因为某些props依赖于子类内容
    // 已经渲染, 例如 `<select value>`
    // 如果标记判断子节点类型是文本类型
    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
       // 处理子节点是纯文本的情况
      hostSetElementText(el, vnode.children as string)
      //如果标记类型是数组子类
    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
    //挂载子类,进行patch后进行挂载
      mountChildren(
        vnode.children as VNodeArrayChildren,
        el,
        null,
        parentComponent,
        parentSuspense,
        isSVG && type !== &#39;foreignObject&#39;,
        slotScopeIds,
        optimized
      )
    }
    if (dirs) {
      invokeDirectiveHook(vnode, null, parentComponent, &#39;created&#39;)
    }
    // 设置范围id
    setScopeId(el, vnode, vnode.scopeId, slotScopeIds, parentComponent)
    // props相关的处理,比如 class,style,event,key等属性
    if (props) { 
      for (const key in props) { 
        if (key !== &#39;value&#39; && !isReservedProp(key)) {//key值不等于value字符且不是
          hostPatchProp(
            el,
            key,
            null,
            props[key],
            isSVG,
            vnode.children as VNode[],
            parentComponent,
            parentSuspense,
            unmountChildren
          )
        }
      }
      
      if (&#39;value&#39; in props) {
        hostPatchProp(el, &#39;value&#39;, null, props.value)
      }
      if ((vnodeHook = props.onVnodeBeforeMount)) {
        invokeVNodeHook(vnodeHook, parentComponent, vnode)
      }
    }
      Object.defineProperty(el, &#39;__vueParentComponent&#39;, {
        value: parentComponent,
        enumerable: false
      }
    }
    if (dirs) {
      invokeDirectiveHook(vnode, null, parentComponent, &#39;beforeMount&#39;)
    }
    // #1583 对于内部悬念+悬念未解决的情况,进入钩子应该在悬念解决时调用。
    // #1689  对于内部悬念+悬念解决的情况,只需调用它
    const needCallTransitionHooks =
      (!parentSuspense || (parentSuspense && !parentSuspense.pendingBranch)) &&
      transition && !transition.persisted
    if (needCallTransitionHooks) {
      transition!.beforeEnter(el)
    }
	 //把创建的元素el挂载到container容器上。
    hostInsert(el, container, anchor)
    if (
      (vnodeHook = props && props.onVnodeMounted) ||
      needCallTransitionHooks ||
      dirs
    ) {
      queuePostRenderEffect(() => {
        vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
        needCallTransitionHooks && transition!.enter(el)
        dirs && invokeDirectiveHook(vnode, null, parentComponent, &#39;mounted&#39;)
      }, parentSuspense)
    }
  }

mountElement挂载元素主要做了,创建DOM元素节点,处理节点子节点,挂载子节点,同时对props相关处理。

所以根据代码,首先是通过hostCreateElement方法创建了DOM元素节点。

const {createElement:hostCreateElement } = options

是从options这个实参中解构并重命名为hostCreateElement方法的,那么这个实参是从哪里来 需要追溯一下,回到初次渲染开始的流程中去。

Comment Vue3 restitue-t-il les nœuds virtuels sur la page Web pour le rendu initial ?

从这流程图可以清楚的知道,optionscreateElement方法是从nodeOps.ts文件中导出的并传入baseCreateRender()方法内的。

该文件位于:core/packages/runtime-dom/src/nodeOps.ts

createElement: (tag, isSVG, is, props): Element => {
    const el = isSVG
      ? doc.createElementNS(svgNS, tag)
      : doc.createElement(tag, is ? { is } : undefined)
    if (tag === &#39;select&#39; && props && props.multiple != null) {
      ;(el as HTMLSelectElement).setAttribute(&#39;multiple&#39;, props.multiple)
    }
    return el
  },

从中可以看出,其实是调用了底层的DOM API document.createElement创建元素。

说回上面,创建完DOM节点元素之后,接下来是继续判断子节点的类型,如果子节点是文本类型的,则调用处理文本hostSetElementText()方法。

const {setElementText: hostSetElementText} = option
setElementText: (el, text) => {
    el.textContent = text
  },

与前面的createElement一样,setElementText方法是通过设置DOM元素的textContent属性设置文本。

而如果子节点的类型是数组类,则执行mountChildren方法,对子节点进行挂载:

  const mountChildren: MountChildrenFn = (
    children,//子节点数组里的内容
    container,//容器
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    slotScopeIds,
    optimized,//优化标记
    start = 0
  ) => {
  //遍历子节点中的内容
    for (let i = start; i < children.length; i++) {
    //根据优化标记进行判断进行克隆或者节点初始化处理。
      const child = (children[i] = optimized
        ? cloneIfMounted(children[i] as VNode)
        : normalizeVNode(children[i]))
        //执行patch方法,递归挂载child
      patch(
        null,//因为是初次挂载所以没有老的节点
        child,//虚拟子节点
        container,//容器
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    }
  }

子节点的挂载逻辑看起来会非常眼熟,在对children数组进行遍历之后获取到的每一个child,进行预处理后并对其执行挂载方法。 结合之前调用mountChildren()方法传入的实参和其形参之间的对比。

mountChildren(
	vnode.children as VNodeArrayChildren, //节点中子节点的内容
	el,//DOM元素
	null,
	parentComponent,
	parentSuspense,
	isSVG && type !== &#39;foreignObject&#39;,
	slotScopeIds,
	optimized
)
      
const mountChildren: MountChildrenFn = (
	children,//子节点数组里的内容
	container,//容器
	anchor,
	parentComponent,
	parentSuspense,
	isSVG,
	slotScopeIds,
	optimized,//优化标记
	start = 0
  )

明确的对应上了第二个参数是container,而调用mountChildren方法时传入第二个参数的是在调用mountElement()时创建的DOM节点,这样便建立起了父子关系。 而且,后续的继续递归patch(),能深度遍历树的方式,可以完整的把DOM树遍历出来,完成渲染。

处理完节点的后,最后会调用 hostInsert(el, container, anchor)

const {insert: hostInsert} = option
insert: (child, parent, anchor) => {
    parent.insertBefore(child, anchor || null)
},

再次就用调用DOM方法将子类的内容挂载到parent,也就是把child挂载到parent下,完成节点的挂载。

注意点:node.insertBefore(newnode,existingnode)中_existingnode_虽然是可选的对象,但是实际上,在不同的浏览器会有不同的表现形式,所以如果没有existingnode值的情况下,填入null会将新的节点添加到node子节点的尾部。Comment Vue3 restitue-t-il les nœuds virtuels sur la page Web pour le rendu initial ?

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer