1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        【Vue.js】890- Vue 3.0 進(jìn)階之 VNode 探秘

        共 19692字,需瀏覽 40分鐘

         ·

        2021-03-08 12:23

        本文是 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)什么樣?

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

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

        1.1 內(nèi)部屬性

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

        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 應(yīng)用上下文屬性

        appContext: AppContext | null

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

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

        // packages/runtime-core/src/h.ts
        export function h(typeany, 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(typenull, [propsOrChildren])
              }
              // 只包含屬性不含有子元素
              return createVNode(type, propsOrChildren) // h('div', { id: 'foo' })
            } else {
              // 忽略屬性
              return createVNode(typenull, propsOrChildren) // h('div', ['foo'])
            }
          } else {
            if (l > 3) {
              children = Array.prototype.slice.call(arguments2)
            } else if (l === 3 && isVNode(children)) {
              children = [children]
            }
            return createVNode(type, propsOrChildren, children)
          }
        }

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

        const app = createApp({ // 示例一
          render() => h('div''我是阿寶哥')
        })

        const Comp = () => h("p""我是阿寶哥"); // 示例二

        app.component('component-a', { // 示例三
          template"<p>我是阿寶哥</p>"
        })

        示例一和示例二很明顯都使用了 h 函數(shù),而示例三并未看到 hcreateVNode 函數(shù)的身影。為了一探究竟,我們需要借助 Vue 3 Template Explorer 這個(gè)在線工具來編譯一下 "<p>我是阿寶哥</p>" 模板,該模板編譯后的結(jié)果如下(函數(shù)模式):

        // 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"我是阿寶哥"))
          }
        }

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

        // packages/runtime-core/src/vnode.ts
        export function createBlock(
          type: VNodeTypes | ClassComponent,
          props?: Record<stringany> | 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 函數(shù)內(nèi)部,我們終于看到了 createVNode 函數(shù)的身影。顧名思義,該函數(shù)的作用就是用于創(chuàng)建 VNode,接下來我們來分析一下它。

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

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

        3.1 參數(shù)說明

        createVNode 被定義在 runtime-core/src/vnode.ts 文件中:

        // 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
        }

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

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

        由上圖可知,type 參數(shù)支持很多類型,比如常用的 stringVNodeComponent 等。此外,也有一些陌生的面孔,比如 TextComment 、StaticFragment 等類型,它們的定義如下:

        // 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' : undefinedas anyas {
          __isFragment: true
          new (): {
            $props: VNodeProps
          }
        }

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

        // 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: // 處理文本節(jié)點(diǎn)
                processText(n1, n2, container, anchor)
                break
              case Comment: // 處理注釋節(jié)點(diǎn)
                processCommentNode(n1, n2, container, anchor)
                break
              case Static: // 處理靜態(tài)節(jié)點(diǎn)
                if (n1 == null) {
                  mountStaticNode(n2, container, anchor, isSVG)
                } else if (__DEV__) {
                  patchStaticNode(n1, n2, container, isSVG)
                }
                break
              case Fragment: // 處理Fragment節(jié)點(diǎn)
                processFragment(...)
                break
              default:
                if (shapeFlag & ShapeFlags.ELEMENT) { // 元素類型
                  processElement(...)
                } else if (shapeFlag & ShapeFlags.COMPONENT) { // 組件類型
                  processComponent(...)
                } else if (shapeFlag & ShapeFlags.TELEPORT) { // teleport內(nèi)置組件
                  ;(type as typeof TeleportImpl).process(...)
                } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
                  ;(type as typeof SuspenseImpl).process(...)
                }
            }
          }
        }

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

        props 參數(shù)
        function _createVNode(
          type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
          props: (Data & VNodeProps) | null = null,
        ): VNode 
        { ... }

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

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

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

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

        // 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 函數(shù)內(nèi)部涉及較多的處理邏輯,這里我們只分析主要的邏輯:

        // 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類型,比如處理動(dòng)態(tài)組件的場(chǎng)景:<component :is="vnode"/>
          if (isVNode(type)) {
            const cloned = cloneVNode(type, props, true /* mergeRef: true */)
            if (children) {
              normalizeChildren(cloned, children)
            }
            return cloned
          }

          // 類組件規(guī)范化處理
          if (isClassComponent(type)) {
            type = type.__vccOpts
          }

          // 類和樣式規(guī)范化處理
          if (props) {
            // 省略相關(guān)代碼
          }

          // 把vnode的類型信息轉(zhuǎn)換為位圖
          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

          // 創(chuàng)建VNode對(duì)象
          const vnode: VNode = {
            __v_isVNode: true,
            [ReactiveFlags.SKIP]: true,
            type,
            props,
            // ...
          }

          // 子元素規(guī)范化處理
          normalizeChildren(vnode, children)
          return vnode
        }

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

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

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

        // 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, nullString(child))
          }
        }

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

        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 對(duì)象?

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

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

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

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

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

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

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

        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
        }

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

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

        六、參考資源

        • Vue 3 官網(wǎng) - 渲染函數(shù)
        聚焦全棧,專注分享 TypeScript、Web API、前端架構(gòu)等技術(shù)干貨。

        瀏覽 67
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美操逼视频网 | 天堂欧美城 | 国产精品自产拍在线观看 | 91丨国产丨熟女 熟女 | 91丝袜美腿高跟国产极品老师 | 嫩草午夜福利电影 | 日韩午夜成人电影 | 一级黄色录像免费播放 | 中国操逼网 | 天天视频黄 |