Rumah > hujung hadapan web > View.js > Bagaimanakah Vue3 memberikan nod maya ke halaman web untuk pemaparan awal?

Bagaimanakah Vue3 memberikan nod maya ke halaman web untuk pemaparan awal?

WBOY
Lepaskan: 2023-05-10 15:07:06
ke hadapan
1171 orang telah melayarinya

Teks

Kaedah app.mount dalam fungsi createApp ialah proses pemaparan komponen merentas platform standard: buat VNode dahulu, dan kemudian render VNode.

Bagaimanakah Vue3 memberikan nod maya ke halaman web untuk pemaparan awal?

Bilakah fungsi maya akan dicipta dan diberikan?

Semasa proses permulaan vue3, kod sumber ditunjuk oleh createApp() dalam core/packages/runtime-core/src/apiCreateApp.ts

Bagaimanakah Vue3 memberikan nod maya ke halaman web untuk pemaparan awal?

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
  }
}
Salin selepas log masuk

berada dalam lekap Semasa proses, apabila operasi tidak dipasang, const vnode = createVNode(rootComponent as ConcreteComponent,rootProps) mencipta nod maya dan melepasi vnode (nod maya), rootContainer (bekas akar), dan isSVG sebagai parameter ke dalam fungsi render untuk rendering.

Apakah itu VNode?

Nod maya sebenarnya ialah objek JavaScript, digunakan untuk menerangkan DOM.

Di sini anda boleh menulis contoh mudah yang praktikal untuk membantu pemahaman Berikut ialah nod elemen biasa bagi sekeping html

<div class="title" >这是一个标题</div>
Salin selepas log masuk

Bagaimana untuk menggunakan nod maya untuk mewakilinya.

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
}
Salin selepas log masuk

Dokumentasi rasmi di sini memberikan nasihat: Antara muka VNode yang lengkap mengandungi sifat dalaman yang lain, tetapi amat disyorkan untuk mengelak daripada menggunakan sifat ini yang tidak disenaraikan di sini. Ini mengelakkan isu ketidakserasian yang disebabkan oleh perubahan harta dalaman.

vue3 mempunyai klasifikasi jenis vnod yang lebih terperinci. Sebelum mencipta vnod, fahami dahulu shapeFlags Kelas ini mengekod maklumat jenis dengan sewajarnya. Supaya dalam fasa tampalan, pemprosesan logik yang sepadan boleh dilakukan melalui pelbagai jenis. Pada masa yang sama, anda juga boleh melihat bahawa jenis mempunyai elemen, komponen fungsi kaedah, komponen dengan keadaan, subkelas ialah teks, dsb.

Maklumat awal

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
Salin selepas log masuk

Ia digunakan untuk menunjukkan jenis nod maya semasa. Kita boleh menerangkan jenis nod semasa itu sendiri dan jenis nod anaknya dengan melakukan operasi binari pada shapeFlag.

Mengapa menggunakan Vnode?

Kerana vnode boleh mengabstraksi, mengabstrakkan proses pemaparan dan meningkatkan keupayaan pengabstrakan komponen. Kemudian kerana Vue perlu merentas platform, abstraksi nod boleh dilaksanakan melalui platform itu sendiri, menjadikannya lebih mudah untuk dipaparkan pada pelbagai platform. Walau bagaimanapun, perlu diingat bahawa walaupun vnode digunakan, ini tidak bermakna prestasi vnode adalah lebih berfaedah. Sebagai contoh, untuk komponen yang besar, ia adalah jadual dengan beribu-ribu baris Semasa proses pemaparan, membuat vnod sudah semestinya perlu merentasi beribu-ribu ciptaan vnod, dan kemudian merentasi beribu-ribu tampalan Apabila mengemas kini data jadual, pasti akan ketinggalan berlaku. Walaupun anda menggunakan perbezaan dalam tampung untuk mengoptimumkan bilangan operasi DOM, operasi sentiasa diperlukan.

Bagaimana Vnode dicipta?

vue3 menyediakan fungsi h() untuk mencipta vnod:

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

Intipatinya ialah memanggil fungsi createVNode().

 const vnode = createVNode(rootComponent as ConcreteComponent,rootProps)
Salin selepas log masuk

createVNode() terletak dalam 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
  )
}
Salin selepas log masuk

Pemprosesan logik dalaman hanya ditinggalkan, kod yang hanya boleh dijalankan dalam pembangunan persekitaran dialih keluar di sini :

Hakim pertama

  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
  }
Salin selepas log masuk
  // 类组件规范化
  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)
    }
  }
Salin selepas log masuk

dan gabungkannya dengan kelas penghitungan shapeFlags sebelumnya, tetapkan pengekodan yang ditentukan kepada 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
Salin selepas log masuk

dan ke atas, dan proses beberapa atribut nod maya Selepas itu, masukkannya ke dalam fungsi mencipta nod maya asas untuk mencipta objek atribut yang lebih lanjut dan lebih terperinci.

ciptaan permulaan nod maya createBaseVNode

Buat nod maya asas (objek JavaScript), mulakan dan isikan satu siri atribut yang berkaitan.

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
}
Salin selepas log masuk

Ia boleh dilihat bahawa mencipta vnod ialah proses menyeragamkan kandungan dalam props, kemudian mengekod maklumat jenis nod, menyeragamkan dan mengekod maklumat jenis nod anak, dan akhirnya mencipta objek vnod.

memaparkan menjadikan VNode

baseCreateRenderer() dalam objek yang dikembalikan, terdapat fungsi render(), hidrat digunakan untuk pemaparan pelayan dan fungsi createApp. Dalam fungsi baseCreateRenderer(), fungsi render() ditakrifkan dan kandungan pemaparan tidak rumit. Komponen

akan mencetuskan mount() apabila ia dipasang buat kali pertama dan kemas kini seterusnya, dan ini sebenarnya akan memanggil fungsi pemaparan render(). render() Mula-mula ia akan menentukan sama ada nod maya vnod wujud Jika ia tidak wujud, unmount() nyahpasangnya. Jika ia wujud, fungsi patch() akan dipanggil. Oleh itu, boleh disimpulkan bahawa dalam proses patch(), komponen yang berkaitan diproses.

Bagaimanakah Vue3 memberikan nod maya ke halaman web untuk pemaparan awal?

 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//将虚拟节点赋值到容器上
  }
Salin selepas log masuk

tampalan VNode

Berikut ialah lihat pada kod untuk fungsi patch(), memfokuskan pada proses apabila komponen pertama kali dipaparkan.

Bagaimanakah Vue3 memberikan nod maya ke halaman web untuk pemaparan awal?

// 注意:此闭包中的函数应使用 &#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)
    }
  }
Salin selepas log masuk

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

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

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

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

  • 对新节点的类型判断

    • 文本类:processText

    • 注释类:processComment

    • 静态类:mountStaticNode

    • 片段类:processFragment

    • 默认

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

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

Bagaimanakah Vue3 memberikan nod maya ke halaman web untuk pemaparan awal?

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)
    }
  }
Salin selepas log masuk

老节点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//是否优化
    )
  }
Salin selepas log masuk

挂载组件中,除开缓存和悬挂上的函数处理,其逻辑上基本为:创建组件的实例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() 
  }
Salin selepas log masuk

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>
Salin selepas log masuk

而helloWorld组件中是

标签包含一个

标签

<template>
	<div class="hello">
		<p>hello world</p>
	</div>
</template>
Salin selepas log masuk

在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

Bagaimanakah Vue3 memberikan nod maya ke halaman web untuk pemaparan awal?

  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
      )
    }
  }
Salin selepas log masuk

逻辑依旧,如果有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)
    }
  }
Salin selepas log masuk

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

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

const {createElement:hostCreateElement } = options
Salin selepas log masuk

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

Bagaimanakah Vue3 memberikan nod maya ke halaman web untuk pemaparan awal?

从这流程图可以清楚的知道,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
  },
Salin selepas log masuk

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

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

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

与前面的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
      )
    }
  }
Salin selepas log masuk

子节点的挂载逻辑看起来会非常眼熟,在对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
  )
Salin selepas log masuk

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

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

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

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

注意点:node.insertBefore(newnode,existingnode)中_existingnode_虽然是可选的对象,但是实际上,在不同的浏览器会有不同的表现形式,所以如果没有existingnode值的情况下,填入null会将新的节点添加到node子节点的尾部。Bagaimanakah Vue3 memberikan nod maya ke halaman web untuk pemaparan awal?

Atas ialah kandungan terperinci Bagaimanakah Vue3 memberikan nod maya ke halaman web untuk pemaparan awal?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Label berkaitan:
sumber:yisu.com
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan