您好,登錄后才能下訂單哦!
這篇文章主要講解了“怎么使用v-lazy-show編譯模板指令”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“怎么使用v-lazy-show編譯模板指令”吧!
簡單的說,v-lazy-show 是一個編譯時指令,就是對 v-show 的一種優化,因為我們知道,v-show 的原理只是基于簡單的切換 display none,false則為none,true則移除
但即使在第一次條件為 falsy 的時候,其依然會渲染對應的組件,那如果該組件很大,就會帶來額外的渲染開銷,比如我們有個 Tabs,默認初始顯示第一個 tab,但后面的 tab 也都渲染了,只是沒有顯示罷了(實際上沒有必要,因為可能你點都不會點開)。
那基于此種情況下,我們可以優化一下,即第一次條件為 falsy 的情況下,不渲染對應的組件,直到條件為 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 的情況下,對應 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 的,那么就應該在對應的構建工具中配置,如下:
如果你用的是 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,那么應該這樣配置:
// nuxt.config.ts import { transformLazyShow } from 'v-lazy-show' class="brush:js;"export default defineNuxtConfig({ vue: { compilerOptions: { nodeTransforms: [ transformLazyShow, // <--- 加上這行 ], }, }, })
上面的指令作用很好理解,那么其是如何實現的呢?我們看下大佬是怎么做的。具體可見源碼
源碼不多,我這里直接貼出來,再一步步看如何實現(這里快速過一下即可,后面會一步步分析):
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 這個函數,我們先簡單看下其作用:
createStructuralDirectiveTransform 是一個工廠函數,用于創建一個自定義的 transform 函數,用于在編譯過程中處理特定的結構性指令(例如 v-for, v-if, v-else-if, v-else 等)。
該函數有兩個參數:
nameMatcher:一個正則表達式或字符串,用于匹配需要被處理的指令名稱。
fn:一個函數,用于處理結構性指令。該函數有三個參數:
node:當前節點對象。
dir:當前節點上的指令對象。
context:編譯上下文對象,包含編譯期間的各種配置和數據。
createStructuralDirectiveTransform 函數會返回一個函數,該函數接收一個節點對象和編譯上下文對象,用于根據指定的 nameMatcher 匹配到對應的指令后,調用用戶自定義的 fn 函數進行處理。
在編譯過程中,當遇到符合 nameMatcher 的結構性指令時,就會調用返回的處理函數進行處理,例如在本例中,當遇到 v-show 或 v-lazy-show 時,就會調用 transformLazyShow 處理函數進行處理。
if (dir.name === 'show' && !dir.modifiers.includes('lazy')) { return () => { node.props.push(dir) } }
因為 v-show.lazy 是可以生效的,所以 v-show 會進入該方法,但如果僅僅只是 v-show,而沒有 lazy 修飾符,那么實際上不用處理
這里有個細節,為何要將指令對象 push 進 props,不 push 行不行?
原先的表現是 v-show 條件為 false 時 display 為 none,渲染了節點,只是不顯示:
而注釋node.props.push(dir)
后,看看頁面表現咋樣:
v-show 的功能沒了,也就是說指令的功能會添加到 props 上,所以這里要特別注意,不是單純的返回 node 即可。后來還有幾處node.props.push,原理跟這里一樣。
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 是 用來創建一個 vnode 節點的函數:
const body = createVNodeCall( /** 當前的上下文 (context) 對象,即 CodegenContext */ context, /** helper 函數是 Vue 內部使用的幫助函數。FRAGMENT 表示創建 Fragment 節點的 helper 函數 */ helper(FRAGMENT), /** 組件的 props */ undefined, /** 當前節點的子節點數組,即包含有指令的節點本身 */ [node], /** 表示該節點的 PatchFlag,指明了該節點是否穩定、是否具有一些特定的更新行為等。STABLE_FRAGMENT 表示該 Fragment 節點是一個穩定的節點,即其子節點不會發生改變 */ PatchFlags.STABLE_FRAGMENT.toString(), /** 該節點的動態 keys */ undefined, /** 該節點的模板引用 (ref) */ undefined, /** 表示該節點是否需要開啟 Block (塊) 模式,即是否需要對其子節點進行優化 */ true, /** 表示該節點是否是一個 Portal 節點 */ false, /** 表示該節點是否是一個組件 */ false /* isComponent */, /** 該節點在模板中的位置信息 */ node.loc, )
參數含義如下,簡單了解即可(反正看了就忘)
也就是說,其會生成如下模板:
<template> <ExpansiveComponent v-lazy-show="enabled" msg="v-lazy-show" /> </template>
接下來這部分是主要原理,請打起十二分精神。
先在全局維護一個 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放到渲染函數的_cache上(渲染函數的第二個參數,function render(_ctx, _cache)
),即通過_cache.${key}
作為輔助變量。之后會根據 createConditionalExpression 創建一個條件表達式
const wrapNode = createConditionalExpression( createCompoundExpression([`_cache.${key}`, ' || ', dir.exp!]), createSequenceExpression([ createCompoundExpression([`_cache.${key} = true`]), body, ]), // 生成一個注釋節點 `<!--v-show-if-->` createCallExpression(helper(CREATE_COMMENT), [ '"v-show-if"', 'true', ]), )
也就是說, v-lazy-show 初始傳入的條件為 false 時,那么會為你創建一個注釋節點,用來占位:
createCallExpression(helper(CREATE_COMMENT), [ '"v-show-if"', 'true', ])
這個跟 v-if 一樣
直到第一次條件為真時,將 _cache.${key}
置為 true,那么以后的行為就跟 v-show 一致了,上面的 dir.exp 即指令中的條件,如
<div v-show="enabled"/>
enabled 即 exp,表達式的意思。
readme給出的轉換如下:
<template> <div v-lazy-show="foo"> Hello </div> </template>
會轉換為:
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"/>
轉為下面:
<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>
然后將原先節點替換為處理后的 wrapperNode 即可
context.replaceNode(wrapNode)
因為 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>
小結一下:
為每一個使用 v-lazy-show 分配唯一的 key,放到渲染函數內部的_cache上,即借助輔助變量_cache.${key}
當初始條件為 falsy 時不渲染節點,只渲染注釋節點 <!--v-show-if-->
直到條件為真時將其置為 true,之后的表現就跟 v-show 一致了
由于 vue 不認識 v-lazy-show,v-show.lazy,使用要將指令改回 v-show,且過濾掉 lazy 修飾符(如果使用 v-show.lazy 的話)
感謝各位的閱讀,以上就是“怎么使用v-lazy-show編譯模板指令”的內容了,經過本文的學習后,相信大家對怎么使用v-lazy-show編譯模板指令這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。