antfu大佬的v-lazy-show教我学会了怎么编译模板指令

这篇文章主要介绍了antfu大佬的v-lazy-show,我学会了怎么编译模板指令示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

前言

一开始关注到 antfu 是他的一头长发,毕竟留长发的肯定是技术大佬。果不其然,antfu 是个很高产、很 creative 的大佬,我也很喜欢他写的工具,无论是@antfu/eslint-configunocss、还是vitest等等。

而这篇文章故事的起源是,我今天中午逛 github 的时候发现大佬又又又又开了一个新的 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 的一种优化,因为我们知道,v-show 的原理只是基于简单的切换 display none,false则为none,true则移除

但即使在第一次条件为 falsy 的时候,其依然会渲染对应的组件,那如果该组件很大,就会带来额外的渲染开销,比如我们有个 Tabs,默认初始显示第一个 tab,但后面的 tab 也都渲染了,只是没有显示罢了(实际上没有必要,因为可能你点都不会点开)。

那基于此种情况下,我们可以优化一下,即第一次条件为 falsy 的情况下,不渲染对应的组件,直到条件为 truthy 才渲染该组件。

将原本的 v-show 改为 v-lazy-show 或者 v-show.lazy

 class="brush:js;"
 class="brush:js;"

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