亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

antfu大佬的v-lazy-show教我學(xué)會了怎么編譯模板指令

 更新時間:2023年04月17日 14:30:27   作者:暴走老七  
這篇文章主要介紹了antfu大佬的v-lazy-show,我學(xué)會了怎么編譯模板指令示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

一開始關(guān)注到 antfu 是他的一頭長發(fā),畢竟留長發(fā)的肯定是技術(shù)大佬。果不其然,antfu 是個很高產(chǎn)、很 creative 的大佬,我也很喜歡他寫的工具,無論是@antfu/eslint-config、unocss、還是vitest等等。

而這篇文章故事的起源是,我今天中午逛 github 的時候發(fā)現(xiàn)大佬又又又又開了一個新的 repo(這是家常便飯的事),v-lazy-show

看了下是兩天前的,所以好奇點進去看看是什么東東。

介紹是:

A compile-time directive to lazy initialize v-show for Vue. It makes components mount after first truthy value (v-if), and the DOM keep alive when toggling (v-show).

簡單的說,v-lazy-show 是一個編譯時指令,就是對 v-show 的一種優(yōu)化,因為我們知道,v-show 的原理只是基于簡單的切換 display none,false則為none,true則移除

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

那基于此種情況下,我們可以優(yōu)化一下,即第一次條件為 falsy 的情況下,不渲染對應(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 的情況下,對應(yīng) v-show 來說,其依然會渲染,而對于 v-lazy-show 或 v-show.lazy 來說,只有第一次 enabled 為 true 才渲染,避免了不必要的初始渲染開銷

如何使用?

國際慣例,先裝下依賴,這里強烈推薦 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

既然是個編譯時指令,且是處理 vue template 的,那么就應(yīng)該在對應(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, // <--- 加上這行
      ],
    },
  },
})

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

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

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

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;"http:// https://github.com/vuejs/core/blob/f5971468e53683d8a54d9cd11f73d0b95c0e0fb7/packages/compiler-core/src/ast.ts#L28
const NodeTypes = {
  SIMPLE_EXPRESSION: 4,
}
class="brush:js;"http:// https://github.com/vuejs/core/blob/f5971468e53683d8a54d9cd11f73d0b95c0e0fb7/packages/compiler-core/src/ast.ts#L62
const ElementTypes = {
  TEMPLATE: 3,
}
class="brush:js;"http:// 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

因為是處理運行時的指令,那么自然用到了 createStructuralDirectiveTransform 這個函數(shù),我們先簡單看下其作用:

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

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

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

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

  • node:當(dāng)前節(jié)點對象。
  • dir:當(dāng)前節(jié)點上的指令對象。
  • context:編譯上下文對象,包含編譯期間的各種配置和數(shù)據(jù)。

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

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

不處理 v-show

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

因為 v-show.lazy 是可以生效的,所以 v-show 會進入該方法,但如果僅僅只是 v-show,而沒有 lazy 修飾符,那么實際上不用處理

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

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

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

v-show 的功能沒了,也就是說指令的功能會添加到 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é)點包一層 template

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

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

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

也就是說,其會生成如下模板:

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

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

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

先在全局維護一個 map,代碼中叫 indexMap,是一個 WeakMap(不知道 WeakMap 的可以去了解下)。然后為每一個帶有 v-lazy-show 指令的生成一個唯一 key,這里叫做_lazyshow${keyIndex},也就是第一個就是_lazyshow1,第二個是_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ù)的第二個參數(shù),function render(_ctx, _cache)),即通過_cache.${key}作為輔助變量。之后會根據(jù) createConditionalExpression 創(chuàng)建一個條件表達式

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

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

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

這個跟 v-if 一樣

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

<div v-show="enabled"/>

enabled 即 exp,表達式的意思。

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

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

會轉(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)
}

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

<template v-if="_cache._lazyshow1 || enabled">
    <!-- 為true時會把_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時會把_cache._lazyshow2置為true,那么以后的v-if就用于為true了 -->
    <ExpansiveComponent msg="v-lazy-show" v-show.lazy="enabled"/>
</template>
<template v-else>
  <!--v-show-if-->
</template>

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

context.replaceNode(wrapNode)

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

因為 vue 本身是沒有 v-lazy-show 的,v-show 也沒有 lazy 的的修飾符,那么要讓指令生效,就要做到兩個:

  • 將原先的 show-lazy 改名為 show
  • 過濾掉 lazy 的修飾符
node.props.push({
   ...dir,
   modifiers: dir.modifiers.filter(i => i !== 'lazy'),
   name: 'show',
 })

也就變成這樣啦:

<template v-if="_cache._lazyshow1 || enabled">
    <!-- 為true時會把_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時會把_cache._lazyshow2置為true,那么以后的v-if就用于為true了 -->
    <ExpansiveComponent msg="v-show.lazy" v-show="enabled"/>
</template>
<template v-else>
  <!--v-show-if-->
</template>

小結(jié)一下:

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

  • 當(dāng)初始條件為 falsy 時不渲染節(jié)點,只渲染注釋節(jié)點 <!--v-show-if-->
  • 直到條件為真時將其置為 true,之后的表現(xiàn)就跟 v-show 一致了
  • 由于 vue 不認(rèn)識 v-lazy-show,v-show.lazy,使用要將指令改回 v-show,且過濾掉 lazy 修飾符(如果使用 v-show.lazy 的話)

最后

以上就是我對該運行時編譯插件的認(rèn)識了,可以將 repo 拉下來,上面有個 playground,可以自己調(diào)試調(diào)試,說不定有新的認(rèn)識。

好了,文章到此為止,你今天學(xué)廢了嗎?

以上就是antfu大佬的v-lazy-show,我學(xué)會了怎么編譯模板指令的詳細(xì)內(nèi)容,更多關(guān)于v-lazy-show編譯模板指令的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue+tp5實現(xiàn)簡單登錄功能

    vue+tp5實現(xiàn)簡單登錄功能

    這篇文章主要為大家詳細(xì)介紹了vue+tp5實現(xiàn)簡單登錄功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • Electron + vue 打包桌面操作流程詳解

    Electron + vue 打包桌面操作流程詳解

    這篇文章主要介紹了Electron + vue 打包桌面操作流程,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-06-06
  • vue3+ts+echarts實現(xiàn)按需引入和類型界定方式

    vue3+ts+echarts實現(xiàn)按需引入和類型界定方式

    這篇文章主要介紹了vue3+ts+echarts實現(xiàn)按需引入和類型界定方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • vue2.0+webpack環(huán)境的構(gòu)造過程

    vue2.0+webpack環(huán)境的構(gòu)造過程

    本文分步驟給大家介紹了vue2.0+webpack環(huán)境的構(gòu)造過程的相關(guān)資料,非常不錯具有參考借鑒價值,需要的朋友可以參考下
    2016-11-11
  • VUE 自定義取色器組件的實現(xiàn)

    VUE 自定義取色器組件的實現(xiàn)

    本文主要介紹了VUE 自定義取色器組件的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • 關(guān)于vue-cli 3配置打包優(yōu)化要點(推薦)

    關(guān)于vue-cli 3配置打包優(yōu)化要點(推薦)

    這篇文章主要介紹了vue-cli 3配置打包優(yōu)化要點,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • laravel5.4+vue+element簡單搭建的示例代碼

    laravel5.4+vue+element簡單搭建的示例代碼

    本篇文章主要介紹了laravel5.4+vue+element簡單搭建的示例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • vue-router懶加載的3種方式匯總

    vue-router懶加載的3種方式匯總

    這篇文章主要給大家介紹了關(guān)于vue-router懶加載的3種方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-02-02
  • vue slots 組件的組合/分發(fā)實例

    vue slots 組件的組合/分發(fā)實例

    今天小編就為大家分享一篇vue slots 組件的組合/分發(fā)實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-09-09
  • vue中使用Animate.css的教程詳解

    vue中使用Animate.css的教程詳解

    Animate.css這個動畫庫,很多的動畫在這個庫里面都定義好了,我們用的時候可以直接使用里面的類名就可以,下面我們就來看看如何在vue項目中使用Animate.css吧
    2023-08-08

最新評論