中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Vue3怎么將虛擬節點渲染到網頁初次渲染

發布時間:2023-03-07 10:57:03 來源:億速云 閱讀:155 作者:iii 欄目:開發技術

這篇文章主要介紹了Vue3怎么將虛擬節點渲染到網頁初次渲染的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Vue3怎么將虛擬節點渲染到網頁初次渲染文章都會有所收獲,下面我們一起來看看吧。

正文

 createApp函數內部的app.mount方法是一個標準的可跨平臺的組件渲染流程:先創建VNode,再渲染VNode。

Vue3怎么將虛擬節點渲染到網頁初次渲染

何時會進行虛擬函數的創建和渲染?

vue3初始化過程中,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:'div',
	props:{
		class:'title',
		style:{
			fontSize:'16px',
			width:'100px'
		}
	},
	children:'這是一個標題',
	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
  //傳送,將一個組件內部的模板‘傳送'到該組件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 'vue'
h('div', { id: 'foo' })

其本質也是調用 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怎么將虛擬節點渲染到網頁初次渲染

// 注意:此閉包中的函數應使用 'const xxx = () => {}'樣式,以防止被小寫器內聯。
// 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組件中是<div>標簽包含一個<p>標簽

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

在App組件中,<helloWorld> 節點渲染渲染生成的vnode就是 helloWorld組件的initialVNode,而這個組件內部所有的DOM節點就是vnode通過執行renderComponentRoot渲染生成的的subTree。 每個組件渲染的時候都會運行render函數,renderComponentRoot就是去執行render函數創建整個組件內部的vnode,然后進行標準化就得到了該函數的返回結果:子樹vnode。 生成子樹后,接下來就是繼續調用patch函數把子樹vnode掛載到container上去。 回到patch后,就會繼續對子樹vnode進行判斷,例如上面的App組件的根節點是<div>標簽,而對應的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) === 'svg'
    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 !== 'foreignObject',
        slotScopeIds,
        optimized
      )
    }
    if (dirs) {
      invokeDirectiveHook(vnode, null, parentComponent, 'created')
    }
    // 設置范圍id
    setScopeId(el, vnode, vnode.scopeId, slotScopeIds, parentComponent)
    // props相關的處理,比如 class,style,event,key等屬性
    if (props) { 
      for (const key in props) { 
        if (key !== 'value' && !isReservedProp(key)) {//key值不等于value字符且不是
          hostPatchProp(
            el,
            key,
            null,
            props[key],
            isSVG,
            vnode.children as VNode[],
            parentComponent,
            parentSuspense,
            unmountChildren
          )
        }
      }
      
      if ('value' in props) {
        hostPatchProp(el, 'value', null, props.value)
      }
      if ((vnodeHook = props.onVnodeBeforeMount)) {
        invokeVNodeHook(vnodeHook, parentComponent, vnode)
      }
    }
      Object.defineProperty(el, '__vueParentComponent', {
        value: parentComponent,
        enumerable: false
      }
    }
    if (dirs) {
      invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
    }
    // #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, 'mounted')
      }, 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 === 'select' && props && props.multiple != null) {
      ;(el as HTMLSelectElement).setAttribute('multiple', 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 !== 'foreignObject',
	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怎么將虛擬節點渲染到網頁初次渲染”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“Vue3怎么將虛擬節點渲染到網頁初次渲染”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

略阳县| 呈贡县| 呼玛县| 阳西县| 舟曲县| 甘谷县| 凤翔县| 高安市| 云霄县| 甘肃省| 宜昌市| 杭州市| 彭山县| 鄂尔多斯市| 清新县| 于田县| 藁城市| 江阴市| 潢川县| 眉山市| 贺州市| 且末县| 东乡族自治县| 冷水江市| 应用必备| 阿拉善左旗| 左贡县| 定结县| 扎囊县| 邹平县| 宜昌市| 昔阳县| 五家渠市| 华容县| 苏尼特左旗| 洪泽县| 玛沁县| 东海县| 名山县| 乌兰浩特市| 剑阁县|