自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Vue 3.0 進(jìn)階之 VNode 探秘

開發(fā) 前端
本文阿寶哥主要介紹了 VNode 對(duì)象是什么、如何創(chuàng)建 VNode 對(duì)象及如何創(chuàng)建規(guī)范的 VNode 對(duì)象。為了讓大家能夠更深入地理解 h 和 createVNode 函數(shù)的相關(guān)知識(shí),阿寶哥還從源碼的角度分析了 createVNode 函數(shù) 。

[[383721]]

 

本文轉(zhuǎn)載自微信公眾號(hào)「全棧修仙之路」,作者阿寶哥  。轉(zhuǎn)載本文請(qǐng)聯(lián)系全棧修仙之路公眾號(hào)。

本文是 Vue 3.0 進(jìn)階系列 的第五篇文章,在這篇文章中,阿寶哥將介紹 Vue 3 中的核心對(duì)象 —— VNode,該對(duì)象用于描述節(jié)點(diǎn)的信息,它的全稱是虛擬節(jié)點(diǎn)(virtual node)。與 “虛擬節(jié)點(diǎn)” 相關(guān)聯(lián)的另一個(gè)概念是 “虛擬 DOM”,它是我們對(duì)由 Vue 組件樹建立起來的整個(gè) VNode 樹的稱呼。通常一個(gè) Vue 應(yīng)用會(huì)以一棵嵌套的組件樹的形式來組織:

(圖片來源:https://v3.cn.vuejs.org/)

所以 “虛擬 DOM” 對(duì) Vue 應(yīng)用來說,是至關(guān)重要的。而 “虛擬 DOM” 又是由 VNode 組成的,它是 Vue 底層的核心基石。接下來,阿寶哥將帶大家一起來探索 Vue 3 中與 VNode 相關(guān)的一些知識(shí)。

一、VNode 長(zhǎng)什么樣?

  1. // packages/runtime-core/src/vnode.ts 
  2. export interface VNode< 
  3.   HostNode = RendererNode, 
  4.   HostElement = RendererElement, 
  5.   ExtraProps = { [key: string]: any } 
  6. > { 
  7.  // 省略內(nèi)部的屬性 

在 runtime-core/src/vnode.ts 文件中,我們找到了 VNode 的類型定義。通過 VNode 的類型定義可知,VNode 本質(zhì)是一個(gè)對(duì)象,該對(duì)象中按照屬性的作用,分為 5 大類。這里阿寶哥只詳細(xì)介紹其中常見的兩大類型屬性 —— 內(nèi)部屬性 和 DOM 屬性:

1.1 內(nèi)部屬性

  1. __v_isVNode: true // 標(biāo)識(shí)是否為VNode 
  2. [ReactiveFlags.SKIP]: true // 標(biāo)識(shí)VNode不是observable 
  3. type: VNodeTypes // VNode 類型 
  4. props: (VNodeProps & ExtraProps) | null // 屬性信息 
  5. key: string | number | null // 特殊 attribute 主要用在 Vue 的虛擬 DOM 算法 
  6. ref: VNodeNormalizedRef | null // 被用來給元素或子組件注冊(cè)引用信息。 
  7. scopeId: string | null // SFC only 
  8. children: VNodeNormalizedChildren // 保存子節(jié)點(diǎn) 
  9. component: ComponentInternalInstance | null // 指向VNode對(duì)應(yīng)的組件實(shí)例 
  10. dirs: DirectiveBinding[] | null // 保存應(yīng)用在VNode的指令信息 
  11. transition: TransitionHooks<HostElement> | null // 存儲(chǔ)過渡效果信息 

1.2 DOM 屬性

  1. el: HostNode | null // element  
  2. anchor: HostNode | null // fragment anchor 
  3. target: HostElement | null // teleport target 
  4. targetAnchor: HostNode | null // teleport target anchor 
  5. staticCount: number // number of elements contained in a static vnode 

1.3 suspense 屬性

  1. suspense: SuspenseBoundary | null 
  2. ssContent: VNode | null 
  3. ssFallback: VNode | null 

1.4 optimization 屬性

  1. shapeFlag: number 
  2. patchFlag: number 
  3. dynamicProps: string[] | null 
  4. dynamicChildren: VNode[] | null 

1.5 應(yīng)用上下文屬性

  1. appContext: AppContext | null 

二、如何創(chuàng)建 VNode?

要?jiǎng)?chuàng)建 VNode 對(duì)象的話,我們可以使用 Vue 提供的 h 函數(shù)。也許可以更準(zhǔn)確地將其命名為 createVNode(),但由于頻繁使用和簡(jiǎn)潔,它被稱為 h() 。該函數(shù)接受三個(gè)參數(shù):

  1. // packages/runtime-core/src/h.ts 
  2. export function h(type: any, propsOrChildren?: any, children?: any): VNode { 
  3.   const l = arguments.length 
  4.   if (l === 2) {  
  5.     if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {  
  6.       // single vnode without props 
  7.       if (isVNode(propsOrChildren)) { 
  8.         return createVNode(type, null, [propsOrChildren]) 
  9.       } 
  10.       // 只包含屬性不含有子元素 
  11.       return createVNode(type, propsOrChildren) // h('div', { id: 'foo' }) 
  12.     } else { 
  13.       // 忽略屬性 
  14.       return createVNode(type, null, propsOrChildren) // h('div', ['foo']) 
  15.     } 
  16.   } else { 
  17.     if (l > 3) { 
  18.       children = Array.prototype.slice.call(arguments, 2) 
  19.     } else if (l === 3 && isVNode(children)) { 
  20.       children = [children] 
  21.     } 
  22.     return createVNode(type, propsOrChildren, children) 
  23.   } 

觀察以上代碼可知, h 函數(shù)內(nèi)部的主要處理邏輯就是根據(jù)參數(shù)個(gè)數(shù)和參數(shù)類型,執(zhí)行相應(yīng)處理操作,但最終都是通過調(diào)用 createVNode 函數(shù)來創(chuàng)建 VNode 對(duì)象。在開始介紹 createVNode 函數(shù)前,阿寶哥先舉一些實(shí)際開發(fā)中的示例:

  1. const app = createApp({ // 示例一 
  2.   render: () => h('div''我是阿寶哥'
  3. }) 
  4.  
  5. const Comp = () => h("p""我是阿寶哥"); // 示例二 
  6.  
  7. app.component('component-a', { // 示例三 
  8.   template: "<p>我是阿寶哥</p>" 
  9. }) 

示例一和示例二很明顯都使用了 h 函數(shù),而示例三并未看到 h 或 createVNode 函數(shù)的身影。為了一探究竟,我們需要借助 Vue 3 Template Explorer 這個(gè)在線工具來編譯一下 "

<p>我是阿寶哥</p>" 模板,該模板編譯后的結(jié)果如下(函數(shù)模式):

  1. // https://vue-next-template-explorer.netlify.app/ 
  2. const _Vue = Vue 
  3. return function render(_ctx, _cache, $props, $setup, $data, $options) { 
  4.   with (_ctx) { 
  5.     const { createVNode: _createVNode, openBlock: _openBlock, 
  6.       createBlock: _createBlock } = _Vue 
  7.     return (_openBlock(), _createBlock("p"null"我是阿寶哥")) 
  8.   } 

由以上編譯結(jié)果可知, "<p>我是阿寶哥</p>" 模板被編譯生成了一個(gè) render 函數(shù),調(diào)用該函數(shù)后會(huì)返回 createBlock 函數(shù)的調(diào)用結(jié)果。其中 createBlock 函數(shù)的實(shí)現(xiàn)如下所示:

  1. // packages/runtime-core/src/vnode.ts 
  2. export function createBlock( 
  3.   type: VNodeTypes | ClassComponent, 
  4.   props?: Record<string, any> | null
  5.   children?: any
  6.   patchFlag?: number, 
  7.   dynamicProps?: string[] 
  8. ): VNode { 
  9.   const vnode = createVNode( 
  10.     type, 
  11.     props, 
  12.     children, 
  13.     patchFlag, 
  14.     dynamicProps, 
  15.     true /* isBlock: prevent a block from tracking itself */ 
  16.   ) 
  17.   // 省略部分代碼 
  18.   return vnode 

在 createBlock 函數(shù)內(nèi)部,我們終于看到了 createVNode 函數(shù)的身影。顧名思義,該函數(shù)的作用就是用于創(chuàng)建 VNode,接下來我們來分析一下它。

三、createVNode 函數(shù)內(nèi)部做了啥?

下面我們將從參數(shù)說明和邏輯說明兩方面來介紹 createVNode 函數(shù):

3.1 參數(shù)說明

  1. // packages/runtime-core/src/vnode.ts 
  2. export const createVNode = (__DEV__ 
  3.   ? createVNodeWithArgsTransform 
  4.   : _createVNode) as typeof _createVNode 
  5.  
  6. function _createVNode( 
  7.   type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, 
  8.   props: (Data & VNodeProps) | null = null
  9.   children: unknown = null
  10.   patchFlag: number = 0, 
  11.   dynamicProps: string[] | null = null
  12.   isBlockNode = false 
  13. ): VNode { 
  14.   //  
  15.   return vnode 

在分析該函數(shù)的具體代碼前,我們先來看一下它的參數(shù)。該函數(shù)可以接收 6 個(gè)參數(shù),這里阿寶哥用思維導(dǎo)圖來重點(diǎn)介紹前面 2 個(gè)參數(shù):

type 參數(shù)

  1. // packages/runtime-core/src/vnode.ts 
  2. function _createVNode( 
  3.   type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, 
  4.   // 省略其他參數(shù) 
  5. ): VNode { ... } 

由上圖可知,type 參數(shù)支持很多類型,比如常用的 string、VNode 和 Component 等。此外,也有一些陌生的面孔,比如 Text、Comment 、Static 和 Fragment 等類型,它們的定義如下:

  1. // packages/runtime-core/src/vnode.ts 
  2. export const Text = Symbol(__DEV__ ? 'Text' : undefined) 
  3. export const Comment = Symbol(__DEV__ ? 'Comment' : undefined) 
  4. export const Static = Symbol(__DEV__ ? 'Static' : undefined) 
  5.  
  6. export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as anyas { 
  7.   __isFragment: true 
  8.   new (): { 
  9.     $props: VNodeProps 
  10.   } 

那么定義那么多的類型有什么意義呢?這是因?yàn)樵?patch 階段,會(huì)根據(jù)不同的 VNode 類型來執(zhí)行不同的操作:

  1. // packages/runtime-core/src/renderer.ts 
  2. function baseCreateRenderer( 
  3.   options: RendererOptions, 
  4.   createHydrationFns?: typeof createHydrationFunctions 
  5. ): any { 
  6.   const patch: PatchFn = ( 
  7.     n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null
  8.     isSVG = false, optimized = false 
  9.   ) => { 
  10.     // 省略部分代碼 
  11.     const { type, ref, shapeFlag } = n2 
  12.     switch (type) { 
  13.       case Text: // 處理文本節(jié)點(diǎn) 
  14.         processText(n1, n2, container, anchor) 
  15.         break 
  16.       case Comment: // 處理注釋節(jié)點(diǎn) 
  17.         processCommentNode(n1, n2, container, anchor) 
  18.         break 
  19.       case Static: // 處理靜態(tài)節(jié)點(diǎn) 
  20.         if (n1 == null) { 
  21.           mountStaticNode(n2, container, anchor, isSVG) 
  22.         } else if (__DEV__) { 
  23.           patchStaticNode(n1, n2, container, isSVG) 
  24.         } 
  25.         break 
  26.       case Fragment: // 處理Fragment節(jié)點(diǎn) 
  27.         processFragment(...) 
  28.         break 
  29.       default
  30.         if (shapeFlag & ShapeFlags.ELEMENT) { // 元素類型 
  31.           processElement(...) 
  32.         } else if (shapeFlag & ShapeFlags.COMPONENT) { // 組件類型 
  33.           processComponent(...) 
  34.         } else if (shapeFlag & ShapeFlags.TELEPORT) { // teleport內(nèi)置組件 
  35.           ;(type as typeof TeleportImpl).process(...) 
  36.         } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { 
  37.           ;(type as typeof SuspenseImpl).process(...) 
  38.         } 
  39.     } 
  40.   } 

介紹完 type 參數(shù)后,接下來我們來看 props 參數(shù),具體如下圖所示:

props 參數(shù)

  1. function _createVNode( 
  2.   type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, 
  3.   props: (Data & VNodeProps) | null = null
  4. ): VNode { ... } 

props 參數(shù)的類型是聯(lián)合類型,這里我們來分析 Data & VNodeProps 交叉類型:

其中 Data 類型是通過 TypeScript 內(nèi)置的工具類型 Record 來定義的:

  1. export type Data = Record<string, unknown> 
  2. type Record<K extends keyof any, T> = { 
  3.   [P in K]: T; 
  4. }; 

而 VNodeProps 類型是通過類型別名來定義的,除了含有 key 和 ref 屬性之外,其他的屬性主要是定義了與生命周期有關(guān)的鉤子:

  1. // packages/runtime-core/src/vnode.ts 
  2. export type VNodeProps = { 
  3.   key?: string | number 
  4.   ref?: VNodeRef 
  5.  
  6.   // vnode hooks 
  7.   onVnodeBeforeMount?: VNodeMountHook | VNodeMountHook[] 
  8.   onVnodeMounted?: VNodeMountHook | VNodeMountHook[] 
  9.   onVnodeBeforeUpdate?: VNodeUpdateHook | VNodeUpdateHook[] 
  10.   onVnodeUpdated?: VNodeUpdateHook | VNodeUpdateHook[] 
  11.   onVnodeBeforeUnmount?: VNodeMountHook | VNodeMountHook[] 
  12.   onVnodeUnmounted?: VNodeMountHook | VNodeMountHook[] 

3.2 邏輯說明

createVNode 函數(shù)內(nèi)部涉及較多的處理邏輯,這里我們只分析主要的邏輯:

  1. // packages/runtime-core/src/vnode.ts 
  2. function _createVNode( 
  3.   type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, 
  4.   props: (Data & VNodeProps) | null = null
  5.   children: unknown = null
  6.   patchFlag: number = 0, 
  7.   dynamicProps: string[] | null = null
  8.   isBlockNode = false 
  9. ): VNode { 
  10.   // 處理VNode類型,比如處理動(dòng)態(tài)組件的場(chǎng)景:<component :is="vnode"/> 
  11.   if (isVNode(type)) { 
  12.     const cloned = cloneVNode(type, props, true /* mergeRef: true */) 
  13.     if (children) { 
  14.       normalizeChildren(cloned, children) 
  15.     } 
  16.     return cloned 
  17.   } 
  18.  
  19.   // 類組件規(guī)范化處理 
  20.   if (isClassComponent(type)) { 
  21.     type = type.__vccOpts 
  22.   } 
  23.  
  24.   // 類和樣式規(guī)范化處理 
  25.   if (props) { 
  26.     // 省略相關(guān)代碼 
  27.   } 
  28.  
  29.   // 把vnode的類型信息轉(zhuǎn)換為位圖 
  30.   const shapeFlag = isString(type) 
  31.     ? ShapeFlags.ELEMENT // ELEMENT = 1 
  32.     : __FEATURE_SUSPENSE__ && isSuspense(type) 
  33.       ? ShapeFlags.SUSPENSE // SUSPENSE = 1 << 7, 
  34.       : isTeleport(type) 
  35.         ? ShapeFlags.TELEPORT // TELEPORT = 1 << 6, 
  36.         : isObject(type) 
  37.           ? ShapeFlags.STATEFUL_COMPONENT // STATEFUL_COMPONENT = 1 << 2, 
  38.           : isFunction(type) 
  39.             ? ShapeFlags.FUNCTIONAL_COMPONENT // FUNCTIONAL_COMPONENT = 1 << 1, 
  40.             : 0 
  41.  
  42.   // 創(chuàng)建VNode對(duì)象 
  43.   const vnode: VNode = { 
  44.     __v_isVNode: true
  45.     [ReactiveFlags.SKIP]: true
  46.     type, 
  47.     props, 
  48.     // ... 
  49.   } 
  50.  
  51.   // 子元素規(guī)范化處理 
  52.   normalizeChildren(vnode, children) 
  53.   return vnode 

介紹完 createVNode 函數(shù)之后,阿寶哥再來介紹另一個(gè)比較重要的函數(shù) —— normalizeVNode。

四、如何創(chuàng)建規(guī)范的 VNode 對(duì)象?

normalizeVNode 函數(shù)的作用,用于將傳入的 child 參數(shù)轉(zhuǎn)換為規(guī)范的 VNode 對(duì)象。

  1. // packages/runtime-core/src/vnode.ts 
  2. export function normalizeVNode(child: VNodeChild): VNode { 
  3.   if (child == null || typeof child === 'boolean') { // null/undefined/boolean -> Comment 
  4.     return createVNode(Comment) 
  5.   } else if (isArray(child)) { // array -> Fragment 
  6.     return createVNode(Fragment, null, child) 
  7.   } else if (typeof child === 'object') { // VNode -> VNode or mounted VNode -> cloned VNode 
  8.     return child.el === null ? child : cloneVNode(child) 
  9.   } else { // primitive types:'foo' or 1 
  10.     return createVNode(Text, null, String(child)) 
  11.   } 

由以上代碼可知,normalizeVNode 函數(shù)內(nèi)部會(huì)根據(jù) child 參數(shù)的類型進(jìn)行不同的處理:

4.1 null / undefined -> Comment

  1. expect(normalizeVNode(null)).toMatchObject({ type: Comment }) 
  2. expect(normalizeVNode(undefined)).toMatchObject({ type: Comment }) 

4.2 boolean -> Comment

  1. expect(normalizeVNode(true)).toMatchObject({ type: Comment }) 
  2. expect(normalizeVNode(false)).toMatchObject({ type: Comment }) 

4.3 array -> Fragment

  1. expect(normalizeVNode(['foo'])).toMatchObject({ type: Fragment }) 

4.4 VNode -> VNode

  1. const vnode = createVNode('div'
  2. expect(normalizeVNode(vnode)).toBe(vnode) 

4.5 mounted VNode -> cloned VNode

  1. const mounted = createVNode('div'
  2. mounted.el = {} 
  3. const normalized = normalizeVNode(mounted) 
  4. expect(normalized).not.toBe(mounted) 
  5. expect(normalized).toEqual(mounted) 

4.6 primitive types

  1. expect(normalizeVNode('foo')).toMatchObject({ type: Text, children: `foo` }) 
  2. expect(normalizeVNode(1)).toMatchObject({ type: Text, children: `1` }) 

五、阿寶哥有話說

5.1 如何判斷是否為 VNode 對(duì)象?

  1. // packages/runtime-core/src/vnode.ts 
  2. export function isVNode(value: any): value is VNode { 
  3.   return value ? value.__v_isVNode === true : false 

在 VNode 對(duì)象中含有一個(gè) __v_isVNode 內(nèi)部屬性,利用該屬性可以用來判斷當(dāng)前對(duì)象是否為 VNode 對(duì)象。

5.2 如何判斷兩個(gè) VNode 對(duì)象的類型是否相同?

  1. // packages/runtime-core/src/vnode.ts 
  2. export function isSameVNodeType(n1: VNode, n2: VNode): boolean { 
  3.   // 省略__DEV__環(huán)境的處理邏輯 
  4.   return n1.type === n2.type && n1.key === n2.key 

在 Vue 3 中,是通過比較 VNode 對(duì)象的 type 和 key 屬性,來判斷兩個(gè) VNode 對(duì)象的類型是否相同。

5.3 如何快速創(chuàng)建某些類型的 VNode 對(duì)象?

在 Vue 3 內(nèi)部提供了 createTextVNode 、createCommentVNode 和 createStaticVNode 函數(shù)來快速的創(chuàng)建文本節(jié)點(diǎn)、注釋節(jié)點(diǎn)和靜態(tài)節(jié)點(diǎn):

createTextVNode

  1. export function createTextVNode(text: string = ' ', flag: number = 0): VNode { 
  2.   return createVNode(Text, null, text, flag) 

createCommentVNode

  1. export function createCommentVNode( 
  2.   text: string = ''
  3.   asBlock: boolean = false 
  4. ): VNode { 
  5.   return asBlock 
  6.     ? (openBlock(), createBlock(Comment, null, text)) 
  7.     : createVNode(Comment, null, text) 

createStaticVNode

  1. export function createStaticVNode( 
  2.   content: string, 
  3.   numberOfNodes: number 
  4. ): VNode { 
  5.   const vnode = createVNode(Staticnull, content) 
  6.   vnode.staticCount = numberOfNodes 
  7.   return vnode 

本文阿寶哥主要介紹了 VNode 對(duì)象是什么、如何創(chuàng)建 VNode 對(duì)象及如何創(chuàng)建規(guī)范的 VNode 對(duì)象。為了讓大家能夠更深入地理解 h 和 createVNode 函數(shù)的相關(guān)知識(shí),阿寶哥還從源碼的角度分析了 createVNode 函數(shù) 。

在后續(xù)的文章中,阿寶哥將會(huì)介紹 VNode 在 Vue 3 內(nèi)部是如何被使用的,感興趣的小伙伴不要錯(cuò)過喲。

六、參考資源

Vue 3 官網(wǎng) - 渲染函數(shù)

 

責(zé)任編輯:武曉燕 來源: 全棧修仙之路
相關(guān)推薦

2021-02-16 16:41:45

Vue項(xiàng)目指令

2021-02-28 20:41:18

Vue注入Angular

2021-02-19 23:07:02

Vue綁定組件

2021-02-22 21:49:33

Vue動(dòng)態(tài)組件

2021-02-18 08:19:21

Vue自定義Vue 3.0

2021-03-04 22:31:02

Vue進(jìn)階函數(shù)

2021-03-09 22:29:46

Vue 響應(yīng)式API

2021-03-08 00:08:29

Vue應(yīng)用掛載

2021-12-12 18:31:35

VNode組件Vue3

2010-05-11 16:22:40

2020-10-13 08:24:31

Vue3.0系列

2020-04-22 14:15:32

Vue 3.0語法前端

2025-01-22 13:05:58

2011-05-20 09:43:23

JDK7

2011-05-20 09:35:22

JDK7

2022-03-09 09:00:41

SwiftUI視圖生成器Swift

2009-06-30 16:46:45

Criteria進(jìn)階查

2022-03-01 09:01:56

SwiftUI動(dòng)畫進(jìn)階Canvas

2020-08-25 09:50:35

Vue3.0命令前端

2022-02-06 22:13:47

VueVue3.0Vue項(xiàng)目
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)