溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么使用v-lazy-show編譯模板指令

發(fā)布時(shí)間:2023-04-17 15:42:45 來源:億速云 閱讀:146 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要講解了“怎么使用v-lazy-show編譯模板指令”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“怎么使用v-lazy-show編譯模板指令”吧!

    前言

    簡(jiǎn)單的說,v-lazy-show 是一個(gè)編譯時(shí)指令,就是對(duì) v-show 的一種優(yōu)化,因?yàn)槲覀冎?,v-show 的原理只是基于簡(jiǎn)單的切換 display none,false則為none,true則移除

    但即使在第一次條件為 falsy 的時(shí)候,其依然會(huì)渲染對(duì)應(yīng)的組件,那如果該組件很大,就會(huì)帶來額外的渲染開銷,比如我們有個(gè) Tabs,默認(rèn)初始顯示第一個(gè) tab,但后面的 tab 也都渲染了,只是沒有顯示罷了(實(shí)際上沒有必要,因?yàn)榭赡苣泓c(diǎn)都不會(huì)點(diǎn)開)。

    那基于此種情況下,我們可以優(yōu)化一下,即第一次條件為 falsy 的情況下,不渲染對(duì)應(yīng)的組件,直到條件為 truthy 才渲染該組件。

    將原本的 v-show 改為 v-lazy-show 或者 v-show.lazy

    <script setup lang="ts">
    import { ref } from 'vue'
    import ExpansiveComponent from './ExpansiveComponent.vue'
    class="brush:js;"const enabled = ref(false)
    </script>
    class="brush:js;"<template>
      <button @click="enabled = !enabled">
        Toggle
      </button>
    class="brush:js;"  <div class="hello-word-wrapper">
        <ExpansiveComponent v-lazy-show="enabled" msg="v-lazy-show" />
        <ExpansiveComponent v-show.lazy="enabled" msg="v-lazy.show" />
    class="brush:js;"    <ExpansiveComponent v-show="enabled" msg="v-show" />
    class="brush:js;"    <ExpansiveComponent v-if="enabled" msg="v-if" />
      </div>
    </template>
    <!-- ExpansiveComponent.vue -->
    <script setup lang="ts">
    import { onMounted } from 'vue'
    class="brush:js;"const props = defineProps({
      msg: {
        type: String,
        required: true,
      },
    })
    class="brush:js;"onMounted(() => {
      console.log(`${props.msg} mounted`)
    })
    </script>
    class="brush:js;"<template>
      <div>
        <div v-for="i in 1000" :key="i">
          Hello {{ msg }}
        </div>
      </div>
    </template>

    ExpansiveComponent 渲染了 1000 行 div,在條件 enabled 初始為 false 的情況下,對(duì)應(yīng) v-show 來說,其依然會(huì)渲染,而對(duì)于 v-lazy-show 或 v-show.lazy 來說,只有第一次 enabled 為 true 才渲染,避免了不必要的初始渲染開銷

    如何使用?

    國際慣例,先裝下依賴,這里強(qiáng)烈推薦 antfu 大佬的 ni。

    npm install v-lazy-show -D
    yarn add v-lazy-show -D
    pnpm add v-lazy-show -D
    ni v-lazy-show -D

    既然是個(gè)編譯時(shí)指令,且是處理 vue template 的,那么就應(yīng)該在對(duì)應(yīng)的構(gòu)建工具中配置,如下:

    如果你用的是 vite,那么配置如下

    // vite.config.ts
    import { defineConfig } from 'vite'
    import { transformLazyShow } from 'v-lazy-show'
    class="brush:js;"export default defineConfig({
      plugins: [
        Vue({
          template: {
            compilerOptions: {
              nodeTransforms: [
                transformLazyShow, // <--- 加在這里
              ],
            },
          },
        }),
      ]
    })

    如果你用的是 Nuxt,那么應(yīng)該這樣配置:

    // nuxt.config.ts
    import { transformLazyShow } from 'v-lazy-show'
    class="brush:js;"export default defineNuxtConfig({
      vue: {
        compilerOptions: {
          nodeTransforms: [
            transformLazyShow, // <--- 加上這行
          ],
        },
      },
    })

    那么,該指令是如何起作用的?

    上面的指令作用很好理解,那么其是如何實(shí)現(xiàn)的呢?我們看下大佬是怎么做的。具體可見源碼

    源碼不多,我這里直接貼出來,再一步步看如何實(shí)現(xiàn)(這里快速過一下即可,后面會(huì)一步步分析):

    import {
      CREATE_COMMENT,
      FRAGMENT,
      createCallExpression,
      createCompoundExpression,
      createConditionalExpression,
      createSequenceExpression,
      createSimpleExpression,
      createStructuralDirectiveTransform,
      createVNodeCall,
      traverseNode,
    } from '@vue/compiler-core'
    class="brush:js;"const indexMap = new WeakMap()
    class="brush:js;"// https://github.com/vuejs/core/blob/f5971468e53683d8a54d9cd11f73d0b95c0e0fb7/packages/compiler-core/src/ast.ts#L28
    const NodeTypes = {
      SIMPLE_EXPRESSION: 4,
    }
    class="brush:js;"// https://github.com/vuejs/core/blob/f5971468e53683d8a54d9cd11f73d0b95c0e0fb7/packages/compiler-core/src/ast.ts#L62
    const ElementTypes = {
      TEMPLATE: 3,
    }
    class="brush:js;"// https://github.com/vuejs/core/blob/f5971468e53683d8a54d9cd11f73d0b95c0e0fb7/packages/shared/src/patchFlags.ts#L19
    const PatchFlags = {
      STABLE_FRAGMENT: 64,
    }
    class="brush:js;"export const transformLazyShow = createStructuralDirectiveTransform(
      /^(lazy-show|show)$/,
      (node, dir, context) => {
        // forward normal `v-show` as-is
        if (dir.name === 'show' && !dir.modifiers.includes('lazy')) {
          return () => {
            node.props.push(dir)
          }
        }
    class="brush:js;"    const directiveName = dir.name === 'show'
          ? 'v-show.lazy'
          : 'v-lazy-show'
    class="brush:js;"    if (node.tagType === ElementTypes.TEMPLATE || node.tag === 'template')
          throw new Error(`${directiveName} can not be used on <template>`)
    class="brush:js;"    if (context.ssr || context.inSSR) {
          // rename `v-lazy-show` to `v-if` in SSR, and let Vue handles it
          node.props.push({
            ...dir,
            exp: dir.exp
              ? createSimpleExpression(dir.exp.loc.source)
              : undefined,
            modifiers: dir.modifiers.filter(i => i !== 'lazy'),
            name: 'if',
          })
          return
        }
    class="brush:js;"    const { helper } = context
        const keyIndex = (indexMap.get(context.root) || 0) + 1
        indexMap.set(context.root, keyIndex)
    class="brush:js;"    const key = `_lazyshow${keyIndex}`
    class="brush:js;"    const body = createVNodeCall(
          context,
          helper(FRAGMENT),
          undefined,
          [node],
          PatchFlags.STABLE_FRAGMENT.toString(),
          undefined,
          undefined,
          true,
          false,
          false /* isComponent */,
          node.loc,
        )
    class="brush:js;"    const wrapNode = createConditionalExpression(
          createCompoundExpression([`_cache.${key}`, ' || ', dir.exp!]),
          createSequenceExpression([
            createCompoundExpression([`_cache.${key} = true`]),
            body,
          ]),
          createCallExpression(helper(CREATE_COMMENT), [
            '"v-show-if"',
            'true',
          ]),
        ) as any
    class="brush:js;"    context.replaceNode(wrapNode)
    class="brush:js;"    return () => {
          if (!node.codegenNode)
            traverseNode(node, context)
    class="brush:js;"      // rename `v-lazy-show` to `v-show` and let Vue handles it
          node.props.push({
            ...dir,
            modifiers: dir.modifiers.filter(i => i !== 'lazy'),
            name: 'show',
          })
        }
      },
    )

    createStructuralDirectiveTransform

    因?yàn)槭翘幚磉\(yùn)行時(shí)的指令,那么自然用到了 createStructuralDirectiveTransform 這個(gè)函數(shù),我們先簡(jiǎn)單看下其作用:

    createStructuralDirectiveTransform 是一個(gè)工廠函數(shù),用于創(chuàng)建一個(gè)自定義的 transform 函數(shù),用于在編譯過程中處理特定的結(jié)構(gòu)性指令(例如 v-for, v-if, v-else-if, v-else 等)。

    該函數(shù)有兩個(gè)參數(shù):

    nameMatcher:一個(gè)正則表達(dá)式或字符串,用于匹配需要被處理的指令名稱。

    fn:一個(gè)函數(shù),用于處理結(jié)構(gòu)性指令。該函數(shù)有三個(gè)參數(shù):

    • node:當(dāng)前節(jié)點(diǎn)對(duì)象。

    • dir:當(dāng)前節(jié)點(diǎn)上的指令對(duì)象。

    • context:編譯上下文對(duì)象,包含編譯期間的各種配置和數(shù)據(jù)。

    createStructuralDirectiveTransform 函數(shù)會(huì)返回一個(gè)函數(shù),該函數(shù)接收一個(gè)節(jié)點(diǎn)對(duì)象和編譯上下文對(duì)象,用于根據(jù)指定的 nameMatcher 匹配到對(duì)應(yīng)的指令后,調(diào)用用戶自定義的 fn 函數(shù)進(jìn)行處理。

    在編譯過程中,當(dāng)遇到符合 nameMatcher 的結(jié)構(gòu)性指令時(shí),就會(huì)調(diào)用返回的處理函數(shù)進(jìn)行處理,例如在本例中,當(dāng)遇到 v-show 或 v-lazy-show 時(shí),就會(huì)調(diào)用 transformLazyShow 處理函數(shù)進(jìn)行處理。

    不處理 v-show

    if (dir.name === 'show' && !dir.modifiers.includes('lazy')) {
        return () => {
          node.props.push(dir)
        }
      }

    因?yàn)?v-show.lazy 是可以生效的,所以 v-show 會(huì)進(jìn)入該方法,但如果僅僅只是 v-show,而沒有 lazy 修飾符,那么實(shí)際上不用處理

    這里有個(gè)細(xì)節(jié),為何要將指令對(duì)象 push 進(jìn) props,不 push 行不行?

    原先的表現(xiàn)是 v-show 條件為 false 時(shí) display 為 none,渲染了節(jié)點(diǎn),只是不顯示:

    怎么使用v-lazy-show編譯模板指令

    而注釋node.props.push(dir)后,看看頁面表現(xiàn)咋樣:

    怎么使用v-lazy-show編譯模板指令

    v-show 的功能沒了,也就是說指令的功能會(huì)添加到 props 上,所以這里要特別注意,不是單純的返回 node 即可。后來還有幾處node.props.push,原理跟這里一樣。

    服務(wù)端渲染目前是轉(zhuǎn)為 v-if

    if (context.ssr || context.inSSR) {
          // rename `v-lazy-show` to `v-if` in SSR, and let Vue handles it
      node.props.push({
        ...dir,
        exp: dir.exp
          ? createSimpleExpression(dir.exp.loc.source)
          : undefined,
        modifiers: dir.modifiers.filter(i => i !== 'lazy'),
        name: 'if',
      })
      return
    }

    將 v-lazy-show 改名為 v-if,且過濾掉修飾符

    createVNodeCall 給原先節(jié)點(diǎn)包一層 template

    顧名思義,createVNodeCall 是 用來創(chuàng)建一個(gè) vnode 節(jié)點(diǎn)的函數(shù):

    const body = createVNodeCall(
          /** 當(dāng)前的上下文 (context) 對(duì)象,即 CodegenContext */
          context,
          /** helper 函數(shù)是 Vue 內(nèi)部使用的幫助函數(shù)。FRAGMENT 表示創(chuàng)建 Fragment 節(jié)點(diǎn)的 helper 函數(shù) */
          helper(FRAGMENT),
          /** 組件的 props */
          undefined,
          /** 當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)數(shù)組,即包含有指令的節(jié)點(diǎn)本身 */
          [node],
          /** 表示該節(jié)點(diǎn)的 PatchFlag,指明了該節(jié)點(diǎn)是否穩(wěn)定、是否具有一些特定的更新行為等。STABLE_FRAGMENT 表示該 Fragment 節(jié)點(diǎn)是一個(gè)穩(wěn)定的節(jié)點(diǎn),即其子節(jié)點(diǎn)不會(huì)發(fā)生改變 */
          PatchFlags.STABLE_FRAGMENT.toString(),
          /** 該節(jié)點(diǎn)的動(dòng)態(tài) keys */
          undefined,
          /** 該節(jié)點(diǎn)的模板引用 (ref) */
          undefined,
          /** 表示該節(jié)點(diǎn)是否需要開啟 Block (塊) 模式,即是否需要對(duì)其子節(jié)點(diǎn)進(jìn)行優(yōu)化 */
          true,
          /** 表示該節(jié)點(diǎn)是否是一個(gè) Portal 節(jié)點(diǎn) */
          false,
          /** 表示該節(jié)點(diǎn)是否是一個(gè)組件 */
          false /* isComponent */,
          /** 該節(jié)點(diǎn)在模板中的位置信息 */
          node.loc,
    )

    參數(shù)含義如下,簡(jiǎn)單了解即可(反正看了就忘)

    也就是說,其會(huì)生成如下模板:

    <template>
      <ExpansiveComponent v-lazy-show="enabled" msg="v-lazy-show" />
    </template>

    關(guān)鍵代碼(重點(diǎn))

    接下來這部分是主要原理,請(qǐng)打起十二分精神。

    先在全局維護(hù)一個(gè) map,代碼中叫 indexMap,是一個(gè) WeakMap(不知道 WeakMap 的可以去了解下)。然后為每一個(gè)帶有 v-lazy-show 指令的生成一個(gè)唯一 key,這里叫做_lazyshow${keyIndex},也就是第一個(gè)就是_lazyshow1,第二個(gè)是_lazyshow2...

      const keyIndex = (indexMap.get(context.root) || 0) + 1
      indexMap.set(context.root, keyIndex)
    class="brush:js;"  const key = `_lazyshow${keyIndex}`

    然后將生成的key放到渲染函數(shù)的_cache上(渲染函數(shù)的第二個(gè)參數(shù),function render(_ctx, _cache)),即通過_cache.${key}作為輔助變量。之后會(huì)根據(jù) createConditionalExpression 創(chuàng)建一個(gè)條件表達(dá)式

    const wrapNode = createConditionalExpression(
          createCompoundExpression([`_cache.${key}`, ' || ', dir.exp!]),
          createSequenceExpression([
            createCompoundExpression([`_cache.${key} = true`]),
            body,
          ]),
          // 生成一個(gè)注釋節(jié)點(diǎn) `<!--v-show-if-->`
          createCallExpression(helper(CREATE_COMMENT), [
            '"v-show-if"',
            'true',
          ]),
    )

    也就是說, v-lazy-show 初始傳入的條件為 false 時(shí),那么會(huì)為你創(chuàng)建一個(gè)注釋節(jié)點(diǎn),用來占位:

    createCallExpression(helper(CREATE_COMMENT), [
      '"v-show-if"',
      'true',
    ])

    怎么使用v-lazy-show編譯模板指令

    這個(gè)跟 v-if 一樣

    直到第一次條件為真時(shí),將 _cache.${key} 置為 true,那么以后的行為就跟 v-show 一致了,上面的 dir.exp 即指令中的條件,如

    <div v-show="enabled"/>

    enabled 即 exp,表達(dá)式的意思。

    readme給出的轉(zhuǎn)換如下:

    <template>
      <div v-lazy-show="foo">
        Hello
      </div>
    </template>

    會(huì)轉(zhuǎn)換為:

    import { Fragment as _Fragment, createCommentVNode as _createCommentVNode, createElementBlock as _createElementBlock, createElementVNode as _createElementVNode, openBlock as _openBlock, vShow as _vShow, withDirectives as _withDirectives } from 'vue'
    class="brush:js;"export function render(_ctx, _cache) {
      return (_cache._lazyshow1 || _ctx.foo)
        ? (_cache._lazyshow1 = true, (_openBlock(),
          _withDirectives(_createElementVNode('div', null, ' Hello ', 512 /* NEED_PATCH */), [
            [_vShow, _ctx.foo]
          ])))
        : _createCommentVNode('v-show-if', true)
    }

    你可以簡(jiǎn)單理解為會(huì)將<ExpansiveComponent msg="v-lazy-show" v-lazy-show=""enabled"/>轉(zhuǎn)為下面:

    <template v-if="_cache._lazyshow1 || enabled">
        <!-- 為true時(shí)會(huì)把_cache._lazyshow1置為true,那么以后的v-if就用于為true了 -->
        <ExpansiveComponent msg="v-lazy-show" v-lazy-show="enabled"/>
    </template>
    <template v-else>
      <!--v-show-if-->
    </template>
    class="brush:js;"<template v-if="_cache._lazyshow2 || enabled">
        <!-- 為true時(shí)會(huì)把_cache._lazyshow2置為true,那么以后的v-if就用于為true了 -->
        <ExpansiveComponent msg="v-lazy-show" v-show.lazy="enabled"/>
    </template>
    <template v-else>
      <!--v-show-if-->
    </template>

    然后將原先節(jié)點(diǎn)替換為處理后的 wrapperNode 即可

    context.replaceNode(wrapNode)

    最后將 v-lazy-show | v-shouw.lazy 處理為 v-show

    因?yàn)?vue 本身是沒有 v-lazy-show 的,v-show 也沒有 lazy 的的修飾符,那么要讓指令生效,就要做到兩個(gè):

    • 將原先的 show-lazy 改名為 show

    • 過濾掉 lazy 的修飾符

    node.props.push({
       ...dir,
       modifiers: dir.modifiers.filter(i => i !== 'lazy'),
       name: 'show',
     })

    也就變成這樣啦:

    <template v-if="_cache._lazyshow1 || enabled">
        <!-- 為true時(shí)會(huì)把_cache._lazyshow1置為true,那么以后的v-if就用于為true了 -->
        <ExpansiveComponent msg="v-lazy-show" v-show="enabled"/>
    </template>
    <template v-else>
      <!--v-show-if-->
    </template>
    <template v-if="_cache._lazyshow2 || enabled">
        <!-- 為true時(shí)會(huì)把_cache._lazyshow2置為true,那么以后的v-if就用于為true了 -->
        <ExpansiveComponent msg="v-show.lazy" v-show="enabled"/>
    </template>
    <template v-else>
      <!--v-show-if-->
    </template>

    小結(jié)一下:

    為每一個(gè)使用 v-lazy-show 分配唯一的 key,放到渲染函數(shù)內(nèi)部的_cache上,即借助輔助變量_cache.${key}

    • 當(dāng)初始條件為 falsy 時(shí)不渲染節(jié)點(diǎn),只渲染注釋節(jié)點(diǎn) <!--v-show-if-->

    • 直到條件為真時(shí)將其置為 true,之后的表現(xiàn)就跟 v-show 一致了

    • 由于 vue 不認(rèn)識(shí) v-lazy-show,v-show.lazy,使用要將指令改回 v-show,且過濾掉 lazy 修飾符(如果使用 v-show.lazy 的話)

    感謝各位的閱讀,以上就是“怎么使用v-lazy-show編譯模板指令”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)怎么使用v-lazy-show編譯模板指令這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI