<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 19692字,需瀏覽 40分鐘

           ·

          2021-03-17 09:42

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

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

          所以 “虛擬 DOM” 對(duì) Vue 應(yīng)用來(lái)說(shuō),是至關(guān)重要的。而 “虛擬 DOM” 又是由 VNode 組成的,它是 Vue 底層的核心基石。接下來(lái),阿寶哥將帶大家一起來(lái)探索 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 }
          > {
           // 省略?xún)?nèi)部的屬性
          }

          runtime-core/src/vnode.ts 文件中,我們找到了 VNode 的類(lèi)型定義。通過(guò) VNode 的類(lèi)型定義可知,VNode 本質(zhì)是一個(gè)對(duì)象,該對(duì)象中按照屬性的作用,分為 5 大類(lèi)。這里阿寶哥只詳細(xì)介紹其中常見(jiàn)的兩大類(lèi)型屬性 —— 內(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 類(lèi)型
          props: (VNodeProps & ExtraProps) | null // 屬性信息
          key: string | number | null // 特殊 attribute 主要用在 Vue 的虛擬 DOM 算法
          ref: VNodeNormalizedRef | null // 被用來(lái)給元素或子組件注冊(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ǔ)過(guò)渡效果信息

          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ì)象的話(huà),我們可以使用 Vue 提供的 h 函數(shù)。也許可以更準(zhǔn)確地將其命名為 createVNode(),但由于頻繁使用和簡(jiǎn)潔,它被稱(chēng)為 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ù)類(lèi)型,執(zhí)行相應(yīng)處理操作,但最終都是通過(guò)調(diào)用 createVNode 函數(shù)來(lái)創(chuàng)建 VNode 對(duì)象。在開(kāi)始介紹 createVNode 函數(shù)前,阿寶哥先舉一些實(shí)際開(kāi)發(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è)在線(xiàn)工具來(lái)編譯一下 "<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,接下來(lái)我們來(lái)分析一下它。

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

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

          3.1 參數(shù)說(shuō)明

          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ù)的具體代碼前,我們先來(lái)看一下它的參數(shù)。該函數(shù)可以接收 6 個(gè)參數(shù),這里阿寶哥用思維導(dǎo)圖來(lái)重點(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ù)支持很多類(lèi)型,比如常用的 string、VNodeComponent 等。此外,也有一些陌生的面孔,比如 Text、CommentStaticFragment 等類(lèi)型,它們的定義如下:

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

          那么定義那么多的類(lèi)型有什么意義呢?這是因?yàn)樵?patch 階段,會(huì)根據(jù)不同的 VNode 類(lèi)型來(lái)執(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) { // 元素類(lèi)型
                    processElement(...)
                  } else if (shapeFlag & ShapeFlags.COMPONENT) { // 組件類(lèi)型
                    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ù)后,接下來(lái)我們來(lái)看 props 參數(shù),具體如下圖所示:

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

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

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

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

          VNodeProps 類(lèi)型是通過(guò)類(lèi)型別名來(lái)定義的,除了含有 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 邏輯說(shuō)明

          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類(lèi)型,比如處理動(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
            }

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

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

            // 把vnode的類(lèi)型信息轉(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ù)之后,阿寶哥再來(lái)介紹另一個(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ù)的類(lèi)型進(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` })

          五、阿寶哥有話(huà)說(shuō)

          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)部屬性,利用該屬性可以用來(lái)判斷當(dāng)前對(duì)象是否為 VNode 對(duì)象。

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

          // 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 中,是通過(guò)比較 VNode 對(duì)象的 typekey 屬性,來(lái)判斷兩個(gè) VNode 對(duì)象的類(lèi)型是否相同。

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

          在 Vue 3 內(nèi)部提供了 createTextVNode 、createCommentVNodecreateStaticVNode 函數(shù)來(lái)快速的創(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ò)過(guò)喲。

          六、參考資源

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

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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草成人电影 | Riri成人网站下载 | 丁香五月新网站 | 人人操人人射人人色 | 大香蕉久久久久久成人草 |