您好,登錄后才能下訂單哦!
這篇文章主要講解了“Vue 3.0進階之如何使用VNode”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Vue 3.0進階之如何使用VNode”吧!
一、VNode 長什么樣?
// packages/runtime-core/src/vnode.ts export interface VNode< HostNode = RendererNode, HostElement = RendererElement, ExtraProps = { [key: string]: any } > { // 省略內部的屬性 }
在 runtime-core/src/vnode.ts 文件中,我們找到了 VNode 的類型定義。通過 VNode 的類型定義可知,VNode 本質是一個對象,該對象中按照屬性的作用,分為 5 大類。這里阿寶哥只詳細介紹其中常見的兩大類型屬性 —— 內部屬性 和 DOM 屬性:
1.1 內部屬性
__v_isVNode: true // 標識是否為VNode [ReactiveFlags.SKIP]: true // 標識VNode不是observable type: VNodeTypes // VNode 類型 props: (VNodeProps & ExtraProps) | null // 屬性信息 key: string | number | null // 特殊 attribute 主要用在 Vue 的虛擬 DOM 算法 ref: VNodeNormalizedRef | null // 被用來給元素或子組件注冊引用信息。 scopeId: string | null // SFC only children: VNodeNormalizedChildren // 保存子節點 component: ComponentInternalInstance | null // 指向VNode對應的組件實例 dirs: DirectiveBinding[] | null // 保存應用在VNode的指令信息 transition: TransitionHooks<HostElement> | null // 存儲過渡效果信息
1.2 DOM 屬性
el: HostNode | null // element anchor: HostNode | null // fragment anchor target: HostElement | null // teleport target targetAnchor: HostNode | null // teleport target anchor staticCount: number // number of elements contained in a static vnode
1.3 suspense 屬性
suspense: SuspenseBoundary | null ssContent: VNode | null ssFallback: VNode | null
1.4 optimization 屬性
shapeFlag: number patchFlag: number dynamicProps: string[] | null dynamicChildren: VNode[] | null
1.5 應用上下文屬性
appContext: AppContext | null
二、如何創建 VNode?
要創建 VNode 對象的話,我們可以使用 Vue 提供的 h 函數。也許可以更準確地將其命名為 createVNode(),但由于頻繁使用和簡潔,它被稱為 h() 。該函數接受三個參數:
// packages/runtime-core/src/h.ts export function h(type: any, propsOrChildren?: any, children?: any): VNode { const l = arguments.length if (l === 2) { if (isObject(propsOrChildren) && !isArray(propsOrChildren)) { // single vnode without props if (isVNode(propsOrChildren)) { return createVNode(type, null, [propsOrChildren]) } // 只包含屬性不含有子元素 return createVNode(type, propsOrChildren) // h('div', { id: 'foo' }) } else { // 忽略屬性 return createVNode(type, null, propsOrChildren) // h('div', ['foo']) } } else { if (l > 3) { children = Array.prototype.slice.call(arguments, 2) } else if (l === 3 && isVNode(children)) { children = [children] } return createVNode(type, propsOrChildren, children) } }
觀察以上代碼可知, h 函數內部的主要處理邏輯就是根據參數個數和參數類型,執行相應處理操作,但最終都是通過調用 createVNode 函數來創建 VNode 對象。在開始介紹 createVNode 函數前,阿寶哥先舉一些實際開發中的示例:
const app = createApp({ // 示例一 render: () => h('div', '我是阿寶哥') }) const Comp = () => h("p", "我是阿寶哥"); // 示例二 app.component('component-a', { // 示例三 template: "<p>我是阿寶哥</p>" })
示例一和示例二很明顯都使用了 h 函數,而示例三并未看到 h 或 createVNode 函數的身影。為了一探究竟,我們需要借助 Vue 3 Template Explorer 這個在線工具來編譯一下 "
<p>我是阿寶哥</p>" 模板,該模板編譯后的結果如下(函數模式):
// https://vue-next-template-explorer.netlify.app/ const _Vue = Vue return function render(_ctx, _cache, $props, $setup, $data, $options) { with (_ctx) { const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue return (_openBlock(), _createBlock("p", null, "我是阿寶哥")) } }
由以上編譯結果可知, "<p>我是阿寶哥</p>" 模板被編譯生成了一個 render 函數,調用該函數后會返回 createBlock 函數的調用結果。其中 createBlock 函數的實現如下所示:
// packages/runtime-core/src/vnode.ts export function createBlock( type: VNodeTypes | ClassComponent, props?: Record<string, any> | null, children?: any, patchFlag?: number, dynamicProps?: string[] ): VNode { const vnode = createVNode( type, props, children, patchFlag, dynamicProps, true /* isBlock: prevent a block from tracking itself */ ) // 省略部分代碼 return vnode }
在 createBlock 函數內部,我們終于看到了 createVNode 函數的身影。顧名思義,該函數的作用就是用于創建 VNode,接下來我們來分析一下它。
三、createVNode 函數內部做了啥?
下面我們將從參數說明和邏輯說明兩方面來介紹 createVNode 函數:
3.1 參數說明
// packages/runtime-core/src/vnode.ts export const createVNode = (__DEV__ ? createVNodeWithArgsTransform : _createVNode) as typeof _createVNode function _createVNode( type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, props: (Data & VNodeProps) | null = null, children: unknown = null, patchFlag: number = 0, dynamicProps: string[] | null = null, isBlockNode = false ): VNode { // return vnode }
在分析該函數的具體代碼前,我們先來看一下它的參數。該函數可以接收 6 個參數,這里阿寶哥用思維導圖來重點介紹前面 2 個參數:
type 參數
// packages/runtime-core/src/vnode.ts function _createVNode( type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, // 省略其他參數 ): VNode { ... }
由上圖可知,type 參數支持很多類型,比如常用的 string、VNode 和 Component 等。此外,也有一些陌生的面孔,比如 Text、Comment 、Static 和 Fragment 等類型,它們的定義如下:
// packages/runtime-core/src/vnode.ts export const Text = Symbol(__DEV__ ? 'Text' : undefined) export const Comment = Symbol(__DEV__ ? 'Comment' : undefined) export const Static = Symbol(__DEV__ ? 'Static' : undefined) export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as { __isFragment: true new (): { $props: VNodeProps } }
那么定義那么多的類型有什么意義呢?這是因為在 patch 階段,會根據不同的 VNode 類型來執行不同的操作:
// packages/runtime-core/src/renderer.ts function baseCreateRenderer( options: RendererOptions, createHydrationFns?: typeof createHydrationFunctions ): any { const patch: PatchFn = ( n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false ) => { // 省略部分代碼 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) } else if (__DEV__) { patchStaticNode(n1, n2, container, isSVG) } break case Fragment: // 處理Fragment節點 processFragment(...) break default: if (shapeFlag & ShapeFlags.ELEMENT) { // 元素類型 processElement(...) } else if (shapeFlag & ShapeFlags.COMPONENT) { // 組件類型 processComponent(...) } else if (shapeFlag & ShapeFlags.TELEPORT) { // teleport內置組件 ;(type as typeof TeleportImpl).process(...) } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { ;(type as typeof SuspenseImpl).process(...) } } } }
介紹完 type 參數后,接下來我們來看 props 參數,具體如下圖所示:
props 參數
function _createVNode( type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, props: (Data & VNodeProps) | null = null, ): VNode { ... }
props 參數的類型是聯合類型,這里我們來分析 Data & VNodeProps 交叉類型:
其中 Data 類型是通過 TypeScript 內置的工具類型 Record 來定義的:
export type Data = Record<string, unknown> type Record<K extends keyof any, T> = { [P in K]: T; };
而 VNodeProps 類型是通過類型別名來定義的,除了含有 key 和 ref 屬性之外,其他的屬性主要是定義了與生命周期有關的鉤子:
// packages/runtime-core/src/vnode.ts export type VNodeProps = { key?: string | number ref?: VNodeRef // vnode hooks onVnodeBeforeMount?: VNodeMountHook | VNodeMountHook[] onVnodeMounted?: VNodeMountHook | VNodeMountHook[] onVnodeBeforeUpdate?: VNodeUpdateHook | VNodeUpdateHook[] onVnodeUpdated?: VNodeUpdateHook | VNodeUpdateHook[] onVnodeBeforeUnmount?: VNodeMountHook | VNodeMountHook[] onVnodeUnmounted?: VNodeMountHook | VNodeMountHook[] }
3.2 邏輯說明
createVNode 函數內部涉及較多的處理邏輯,這里我們只分析主要的邏輯:
// packages/runtime-core/src/vnode.ts function _createVNode( type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, props: (Data & VNodeProps) | null = null, children: unknown = null, patchFlag: number = 0, dynamicProps: string[] | null = null, isBlockNode = false ): VNode { // 處理VNode類型,比如處理動態組件的場景:<component :is="vnode"/> if (isVNode(type)) { const cloned = cloneVNode(type, props, true /* mergeRef: true */) if (children) { normalizeChildren(cloned, children) } return cloned } // 類組件規范化處理 if (isClassComponent(type)) { type = type.__vccOpts } // 類和樣式規范化處理 if (props) { // 省略相關代碼 } // 把vnode的類型信息轉換為位圖 const shapeFlag = isString(type) ? ShapeFlags.ELEMENT // ELEMENT = 1 : __FEATURE_SUSPENSE__ && isSuspense(type) ? ShapeFlags.SUSPENSE // SUSPENSE = 1 << 7, : isTeleport(type) ? ShapeFlags.TELEPORT // TELEPORT = 1 << 6, : isObject(type) ? ShapeFlags.STATEFUL_COMPONENT // STATEFUL_COMPONENT = 1 << 2, : isFunction(type) ? ShapeFlags.FUNCTIONAL_COMPONENT // FUNCTIONAL_COMPONENT = 1 << 1, : 0 // 創建VNode對象 const vnode: VNode = { __v_isVNode: true, [ReactiveFlags.SKIP]: true, type, props, // ... } // 子元素規范化處理 normalizeChildren(vnode, children) return vnode }
介紹完 createVNode 函數之后,阿寶哥再來介紹另一個比較重要的函數 —— normalizeVNode。
四、如何創建規范的 VNode 對象?
normalizeVNode 函數的作用,用于將傳入的 child 參數轉換為規范的 VNode 對象。
// packages/runtime-core/src/vnode.ts export function normalizeVNode(child: VNodeChild): VNode { if (child == null || typeof child === 'boolean') { // null/undefined/boolean -> Comment return createVNode(Comment) } else if (isArray(child)) { // array -> Fragment return createVNode(Fragment, null, child) } else if (typeof child === 'object') { // VNode -> VNode or mounted VNode -> cloned VNode return child.el === null ? child : cloneVNode(child) } else { // primitive types:'foo' or 1 return createVNode(Text, null, String(child)) } }
由以上代碼可知,normalizeVNode 函數內部會根據 child 參數的類型進行不同的處理:
4.1 null / undefined -> Comment
expect(normalizeVNode(null)).toMatchObject({ type: Comment }) expect(normalizeVNode(undefined)).toMatchObject({ type: Comment })
4.2 boolean -> Comment
expect(normalizeVNode(true)).toMatchObject({ type: Comment }) expect(normalizeVNode(false)).toMatchObject({ type: Comment })
4.3 array -> Fragment
expect(normalizeVNode(['foo'])).toMatchObject({ type: Fragment })
4.4 VNode -> VNode
const vnode = createVNode('div') expect(normalizeVNode(vnode)).toBe(vnode)
4.5 mounted VNode -> cloned VNode
const mounted = createVNode('div') mounted.el = {} const normalized = normalizeVNode(mounted) expect(normalized).not.toBe(mounted) expect(normalized).toEqual(mounted)
4.6 primitive types
expect(normalizeVNode('foo')).toMatchObject({ type: Text, children: `foo` }) expect(normalizeVNode(1)).toMatchObject({ type: Text, children: `1` })
五、阿寶哥有話說
5.1 如何判斷是否為 VNode 對象?
// packages/runtime-core/src/vnode.ts export function isVNode(value: any): value is VNode { return value ? value.__v_isVNode === true : false }
在 VNode 對象中含有一個 __v_isVNode 內部屬性,利用該屬性可以用來判斷當前對象是否為 VNode 對象。
5.2 如何判斷兩個 VNode 對象的類型是否相同?
// packages/runtime-core/src/vnode.ts export function isSameVNodeType(n1: VNode, n2: VNode): boolean { // 省略__DEV__環境的處理邏輯 return n1.type === n2.type && n1.key === n2.key }
在 Vue 3 中,是通過比較 VNode 對象的 type 和 key 屬性,來判斷兩個 VNode 對象的類型是否相同。
5.3 如何快速創建某些類型的 VNode 對象?
在 Vue 3 內部提供了 createTextVNode 、createCommentVNode 和 createStaticVNode 函數來快速的創建文本節點、注釋節點和靜態節點:
createTextVNode
export function createTextVNode(text: string = ' ', flag: number = 0): VNode { return createVNode(Text, null, text, flag) }
createCommentVNode
export function createCommentVNode( text: string = '', asBlock: boolean = false ): VNode { return asBlock ? (openBlock(), createBlock(Comment, null, text)) : createVNode(Comment, null, text) }
createStaticVNode
export function createStaticVNode( content: string, numberOfNodes: number ): VNode { const vnode = createVNode(Static, null, content) vnode.staticCount = numberOfNodes return vnode }
感謝各位的閱讀,以上就是“Vue 3.0進階之如何使用VNode”的內容了,經過本文的學習后,相信大家對Vue 3.0進階之如何使用VNode這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。