> 웹 프론트엔드 > View.js > Vue3는 초기 렌더링을 위해 가상 노드를 웹 페이지에 어떻게 렌더링합니까?

Vue3는 초기 렌더링을 위해 가상 노드를 웹 페이지에 어떻게 렌더링합니까?

WBOY
풀어 주다: 2023-05-10 15:07:06
앞으로
1172명이 탐색했습니다.

Text

createApp 함수 내의 app.mount 메서드는 표준 크로스 플랫폼 구성 요소 렌더링 프로세스입니다. 먼저 VNode를 만든 다음 VNode를 렌더링합니다.

Vue3는 초기 렌더링을 위해 가상 노드를 웹 페이지에 어떻게 렌더링합니까?

가상 기능은 언제 생성되고 렌더링되나요?

vue3의 초기화 프로세스 중에 createApp()이 가리키는 소스 코드 core/packages/runtime-core/src/apiCreateApp.ts createApp()指向的源码 core/packages/runtime-core/src/apiCreateApp.ts中

Vue3는 초기 렌더링을 위해 가상 노드를 웹 페이지에 어떻게 렌더링합니까?

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()的过程中,有关组件相关处理。

Vue3는 초기 렌더링을 위해 가상 노드를 웹 페이지에 어떻게 렌더링합니까?

 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()

Vue3는 웹 페이지의 초기 렌더링에 가상 노드를 어떻게 렌더링합니까?

// 注意:此闭包中的函数应使用 &#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)
    }
  }
로그인 후 복사
로그인 후 복사
Vue3는 초기 렌더링을 위해 가상 노드를 웹 페이지에 어떻게 렌더링합니까?마운트 과정에서 작업이 마운트되지 않은 경우 const vnode = createVNode(rootComponent as ConcreteComponent,rootProps)는 가상 노드를 생성하고 vnode(가상 노드), rootContainer(루트 컨테이너) 및 isSVG를 렌더링을 위해 렌더링 함수에 매개변수로 전달합니다.

🎜VNode란 무엇인가요? 🎜🎜가상 노드는 실제로 DOM을 설명하는 데 사용되는 JavaScript의 객체입니다. 🎜🎜여기서 이해를 돕기 위해 실용적이고 간단한 예를 작성할 수 있습니다. 다음은 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)
    }
  }
로그인 후 복사
로그인 후 복사
🎜가상 노드를 사용하여 표현하는 방법은 무엇입니까? 🎜
  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//是否优化
    )
  }
로그인 후 복사
로그인 후 복사
🎜공식 문서에는 다음과 같은 조언이 나와 있습니다. 전체 VNode 인터페이스에는 다른 내부 속성이 포함되어 있지만 여기에 나열되지 않은 이러한 속성은 사용하지 않는 것이 좋습니다. 이렇게 하면 내부 속성 변경으로 인한 비호환성 문제를 방지할 수 있습니다. 🎜🎜vue3에는 vnode 유형에 대한 더 자세한 분류가 있습니다. vnode를 생성하기 전에 먼저 shapeFlags를 이해하세요. 이 클래스는 그에 따라 유형 정보를 인코딩합니다. 따라서 패치 단계에서는 서로 다른 유형을 통해 해당 논리적 처리를 수행할 수 있습니다. 동시에 유형에는 요소가 있고, 메소드 함수 구성요소, 상태가 있는 구성요소, 서브클래스는 텍스트 등이 있음을 알 수 있습니다. 🎜🎜사전 요구 사항🎜

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() 
  }
로그인 후 복사
로그인 후 복사
🎜현재 가상 노드의 유형을 나타내는 데 사용됩니다. shapeFlag에 대해 이진 연산을 수행하여 현재 노드 자체의 유형과 하위 노드의 유형을 설명할 수 있습니다. 🎜🎜Vnode를 사용하는 이유🎜🎜vnode는 렌더링 프로세스를 추상화하고 구성 요소의 추상화 능력을 향상시킬 수 있기 때문입니다. 그런 다음 Vue는 크로스 플랫폼이어야 하기 때문에 노드 추상화를 플랫폼 자체를 통해 구현할 수 있으므로 다양한 플랫폼에서 더 쉽게 렌더링할 수 있습니다. 그러나 vnode를 사용한다고 해서 vnode의 성능이 더 유리하다는 의미는 아닙니다. 예를 들어, 대규모 구성 요소의 경우 렌더링 프로세스 중에 vnode를 생성하려면 필연적으로 수천 개의 vnode 생성을 거쳐야 하며, 테이블 데이터를 업데이트할 때 필연적으로 지연이 발생합니다. 사건이 발생합니다. DOM 작업 수를 최적화하기 위해 패치에서 diff를 사용하더라도 작업은 항상 필요합니다. 🎜🎜Vnode는 어떻게 만들어지나요? 🎜🎜vue3은 vnode를 생성하기 위한 h() 함수를 제공합니다.🎜
<template>
	<div class="app">
		<p>title</p>
		<helloWorld>
	</div>
</template>
로그인 후 복사
로그인 후 복사
🎜핵심은 createVNode() 함수도 호출하는 것입니다. 🎜
<template>
	<div class="hello">
		<p>hello world</p>
	</div>
</template>
로그인 후 복사
로그인 후 복사
🎜createVNode()는 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
      )
    }
  }
로그인 후 복사
로그인 후 복사
🎜에 위치합니다. 내부 로직 처리는 그냥 생략하고, 개발 환경에서만 실행되는 코드는 이렇습니다. : 🎜

첫 번째 심사위원

  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
로그인 후 복사
로그인 후 복사
🎜 이전의 ShapeFlags 열거형 클래스와 결합하여 결정된 인코딩을 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
  },
로그인 후 복사
로그인 후 복사
🎜 위에서 가상 노드의 일부 속성을 처리한 후 이를 전달하여 기본을 생성합니다. virtual 노드 기능에서는 더욱 세밀한 속성 객체 생성이 이루어집니다. 🎜

createBaseVNode 가상 노드 초기화 생성

🎜기본 가상 노드(JavaScript 개체)를 생성하고 일련의 관련 속성을 초기화 및 캡슐화합니다. 🎜
const {setElementText: hostSetElementText} = option
setElementText: (el, text) => {
    el.textContent = text
  },
로그인 후 복사
로그인 후 복사
🎜vnode를 생성한다는 것은 props의 내용을 표준화한 후, 노드 유형 정보를 인코딩하고, 자식 노드의 유형 정보를 표준화 및 인코딩하고, 최종적으로 vnode 객체를 생성하는 과정이라고 볼 수 있습니다. 🎜🎜render는 VNode🎜🎜baseCreateRenderer()를 렌더링합니다. 반환된 객체에는 render() 함수가 있으며, 하이드레이트는 서버 렌더링 및 createApp 함수에 사용됩니다. baseCreateRenderer() 함수에는 render() 함수가 정의되어 있으며, 렌더링 내용은 복잡하지 않습니다. 🎜🎜구성 요소는 처음 마운트되고 후속 업데이트를 위해 mount()를 트리거하며 실제로 render() 렌더링 함수를 호출합니다. render()는 먼저 vnode 가상 노드가 존재하는지 확인합니다. 존재하지 않으면 unmount() 제거 작업을 수행합니다. 존재하는 경우 patch() 함수가 호출됩니다. 따라서 patch() 과정에서 해당 컴포넌트가 처리되는 것으로 유추할 수 있다. 🎜🎜Vue3는 가상 노드를 웹 페이지의 첫 번째 렌더링으로 어떻게 렌더링합니까?🎜
  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🎜🎜여기에서는 구성 요소가 처음으로 렌더링될 때의 프로세스에 초점을 맞춰 patch() 함수에 대한 코드를 살펴보겠습니다. 🎜🎜🎜🎜
// 注意:此闭包中的函数应使用 &#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类型。

Vue3는 초기 렌더링을 위해 가상 노드를 웹 페이지에 어떻게 렌더링합니까?

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

Vue3는 초기 렌더링을 위해 가상 노드를 웹 페이지에 어떻게 렌더링합니까?

  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方法的,那么这个实参是从哪里来 需要追溯一下,回到初次渲染开始的流程中去。

Vue3는 초기 렌더링을 위해 가상 노드를 웹 페이지에 어떻게 렌더링합니까?

从这流程图可以清楚的知道,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子节点的尾部。Vue3는 초기 렌더링을 위해 가상 노드를 웹 페이지에 어떻게 렌더링합니까?

위 내용은 Vue3는 초기 렌더링을 위해 가상 노드를 웹 페이지에 어떻게 렌더링합니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:yisu.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿