阅读视图

发现新文章,点击刷新页面。

依赖自动导入:unplugin-auto-import 完整配置指南

unplugin-auto-import 完整配置指南

目录


简介

unplugin-auto-import 是一个 Vite/Webpack/Rollup 插件,用于自动导入 API,无需手动编写 import 语句。支持 Vue、Vue Router、Pinia 等库,以及自定义工具函数。

主要优势:

  • ✅ 减少重复代码,无需手动导入
  • ✅ 提升开发效率
  • ✅ 支持 TypeScript 类型提示
  • ✅ 支持 ESLint 配置

安装

npm install -D unplugin-auto-import
# 或
yarn add -D unplugin-auto-import
# 或
pnpm add -D unplugin-auto-import

核心配置选项

1. imports - 自动导入库的 API

作用: 指定要自动导入的库或包的 API。

案例 1.1:字符串形式(最简单)
AutoImport({
  imports: [
    'vue',           // 自动导入 ref, computed, onMounted 等
    'vue-router',    // 自动导入 useRoute, useRouter 等
    'pinia',         // 自动导入 defineStore, storeToRefs 等
  ],
})

使用效果:

<script setup>
// 不需要 import,直接使用
const count = ref(0)
const route = useRoute()
const store = defineStore('user', { ... })
</script>
案例 1.2:对象形式(精确控制)
AutoImport({
  imports: [
    'vue',
    // 只导入指定的 API
    {
      'vue-router': ['useRoute', 'useRouter'], // 只导入这两个
    },
    {
      '@vueuse/core': [
        'useMouse',
        'useFetch',
        'useLocalStorage',
      ],
    },
    {
      'axios': [
        ['default', 'axios'], // 导入默认导出并重命名为 axios
      ],
    },
    {
      'lodash-es': [
        'debounce',
        'throttle',
        ['default', '_'], // 默认导出重命名为 _
      ],
    },
  ],
})

使用效果:

<script setup>
// 自动导入指定的 API
const { x, y } = useMouse()
const { data } = useFetch('/api/data')
const debouncedFn = debounce(() => {}, 300)
const throttledFn = throttle(() => {}, 300)
</script>
案例 1.3:带别名的导入
AutoImport({
  imports: [
    {
      'vue': [
        'ref',
        ['computed', 'computedRef'], // 导入 computed 并重命名为 computedRef
        ['onMounted', 'onComponentMount'], // 导入 onMounted 并重命名
      ],
    },
  ],
})

使用效果:

<script setup>
const count = ref(0)
const double = computedRef(() => count.value * 2) // 使用别名
onComponentMount(() => { // 使用别名
  console.log('mounted')
})
</script>

2. dirs - 自动导入自定义目录

作用: 自动导入项目内部目录下的模块(工具函数、composables 等)。

案例 2.1:基本用法
AutoImport({
  dirs: [
    './src/utils',        // 导入 utils 目录下的所有导出
    './src/composables',  // 导入 composables 目录下的所有导出
  ],
})

项目结构:

src/
  utils/
    format.js      // export const formatDate = ...
    validate.js    // export const validateEmail = ...
  composables/
    useAuth.js     // export const useAuth = ...
    useTable.js    // export const useTable = ...

使用效果:

<script setup>
// 自动从 utils 和 composables 导入
const formatted = formatDate(new Date())
const isValid = validateEmail('test@example.com')
const { user, login } = useAuth()
const { data, loading } = useTable()
</script>
案例 2.2:使用 Glob 模式(支持子目录)
AutoImport({
  dirs: [
    './src/utils/**',           // 包含所有子目录
    './src/composables/**',     // 包含所有子目录
    './src/hooks/**/*.ts',      // 只匹配 .ts 文件
  ],
})
案例 2.3:带配置的对象形式
AutoImport({
  dirs: [
    './src/utils',
    {
      // 指定目录
      dir: './src/composables',
      // 是否生成类型声明(默认 true)
      types: true,
      // 文件匹配模式
      pattern: ['**/*.{ts,js}'],
      // 排除某些文件
      ignore: ['**/*.test.ts', '**/*.spec.ts'],
    },
  ],
})
案例 2.4:只导入特定文件
AutoImport({
  dirs: [
    './src/utils/format.js',    // 只导入这个文件
    './src/utils/validate.js', // 只导入这个文件
  ],
})

⚠️ 注意事项:

  • dirs 只能自动导入命名导出export constexport function
  • 不能导入默认导出export default),除非使用 defaultExportByFilename

3. resolvers - 动态解析器

作用: 动态判断某些标识符应该从哪个模块导入(常用于 UI 库)。

案例 3.1:使用 Element Plus Resolver
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

AutoImport({
  resolvers: [
    ElementPlusResolver({
      // 自动导入样式
      importStyle: 'sass', // 或 'css', false
    }),
  ],
})

使用效果:

<script setup>
// 自动导入 ElMessage, ElMessageBox 等
ElMessage.success('成功')
ElMessageBox.confirm('确定?')
</script>
案例 3.2:使用 Vant Resolver
import { VantResolver } from 'unplugin-vue-components/resolvers'

AutoImport({
  resolvers: [
    VantResolver(),
  ],
})

使用效果:

<script setup>
// 自动导入 Vant 的函数式 API
showToast('提示')
showDialog({ message: '内容' })
showNotify({ message: '通知' })
</script>
案例 3.3:自定义 Resolver
AutoImport({
  resolvers: [
    // 自定义 resolver 函数
    (name) => {
      // 如果函数名以 use 开头,从 @/composables 导入
      if (name.startsWith('use')) {
        return {
          name: name,
          from: `@/composables/${name}`,
        }
      }
      // 如果函数名以 $ 开头,从 @/utils 导入
      if (name.startsWith('$')) {
        return {
          name: name.slice(1), // 去掉 $ 前缀
          from: `@/utils/${name.slice(1)}`,
        }
      }
    },
  ],
})

使用效果:

<script setup>
// useAuth 会自动从 @/composables/useAuth 导入
const { user } = useAuth()

// $format 会自动从 @/utils/format 导入(实际导入的是 format)
const date = $format(new Date())
</script>
案例 3.4:多个 Resolver 组合
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'

AutoImport({
  resolvers: [
    ElementPlusResolver(),
    AntDesignVueResolver(),
    // 自定义 resolver
    (name) => {
      if (name === 'myCustomFunction') {
        return { name, from: '@/utils/custom' }
      }
    },
  ],
})

4. include - 包含文件

作用: 指定哪些文件会被插件处理。

案例 4.1:基本用法
AutoImport({
  include: [
    /\.[tj]sx?$/,     // .ts, .tsx, .js, .jsx
    /\.vue$/,         // .vue
    /\.vue\?vue/,     // .vue?vue (SFC)
  ],
})
案例 4.2:只处理特定文件
AutoImport({
  include: [
    /\.vue$/,                    // 只处理 .vue 文件
    /src\/views\/.*\.ts$/,       // 只处理 views 目录下的 .ts 文件
  ],
})
案例 4.3:排除测试文件
AutoImport({
  include: [
    /\.[tj]sx?$/,
    /\.vue$/,
  ],
  exclude: [
    /\.test\.[tj]sx?$/,  // 排除测试文件
    /\.spec\.[tj]sx?$/,  // 排除测试文件
    /node_modules/,      // 排除 node_modules
  ],
})

5. exclude - 排除文件

作用: 排除不需要处理的文件。

案例 5.1:排除特定文件
AutoImport({
  exclude: [
    /node_modules/,           // 排除 node_modules
    /\.test\.[tj]sx?$/,      // 排除测试文件
    /\.spec\.[tj]sx?$/,      // 排除测试文件
    /dist/,                  // 排除构建目录
    /\.d\.ts$/,              // 排除类型声明文件
  ],
})
案例 5.2:排除特定目录
AutoImport({
  exclude: [
    /node_modules/,
    /src\/components\/legacy/, // 排除旧组件目录
    /src\/utils\/deprecated/,  // 排除废弃工具目录
  ],
})

6. dts - 类型声明文件

作用: 生成 TypeScript 类型声明文件,提供类型提示。

案例 6.1:启用类型声明(默认位置)
AutoImport({
  dts: true, // 默认生成到根目录的 auto-imports.d.ts
})
案例 6.2:指定类型声明文件路径
AutoImport({
  dts: './src/auto-imports.d.ts', // 指定生成位置
})
案例 6.3:禁用类型声明
AutoImport({
  dts: false, // 不生成类型声明文件
})

生成的文件示例:

/* eslint-disable */
/* prettier-ignore */
// Generated by unplugin-auto-import
export {}
declare global {
  const ElMessage: typeof import('element-plus')['ElMessage']
  const computed: typeof import('vue')['computed']
  const onMounted: typeof import('vue')['onMounted']
  const ref: typeof import('vue')['ref']
  const useRoute: typeof import('vue-router')['useRoute']
  // ...
}

tsconfig.json 中引入:

{
  "include": [
    "src/auto-imports.d.ts"
  ]
}

7. eslintrc - ESLint 配置

作用: 生成 ESLint 全局变量配置,避免 no-undef 错误。

案例 7.1:基本配置
AutoImport({
  eslintrc: {
    enabled: true,                              // 启用
    filepath: './.eslintrc-auto-import.json',  // 生成文件路径
    globalsPropValue: true,                     // 设置为全局变量
  },
})

生成的 .eslintrc-auto-import.json

{
  "globals": {
    "ElMessage": "readonly",
    "computed": "readonly",
    "onMounted": "readonly",
    "ref": "readonly",
    "useRoute": "readonly"
  }
}

.eslintrc.js 中引入:

module.exports = {
  extends: [
    './.eslintrc-auto-import.json', // 引入自动生成的配置
  ],
}
案例 7.2:自定义配置
AutoImport({
  eslintrc: {
    enabled: true,
    filepath: './.eslintrc-auto-import.json',
    globalsPropValue: 'readonly', // 或 'writable'
  },
})

8. defaultExportByFilename - 按文件名导入

作用: 如果目录下的文件是默认导出,按文件名自动导入。

案例 8.1:启用按文件名导入
AutoImport({
  dirs: ['./src/composables'],
  defaultExportByFilename: true, // 启用
})

文件结构:

src/composables/
  useAuth.js      // export default function useAuth() {}
  useTable.js     // export default function useTable() {}

使用效果:

<script setup>
// 自动从文件名推断导入
const { user } = useAuth()    // 从 useAuth.js 导入
const { data } = useTable()   // 从 useTable.js 导入
</script>

9. vueTemplate - Vue 模板支持

作用: 在 Vue 模板中也能使用自动导入的 API(实验性功能)。

案例 9.1:启用模板支持
AutoImport({
  vueTemplate: true, // 在模板中也能使用自动导入的函数
})

使用效果:

<template>
  <!-- 在模板中直接使用 -->
  <div>{{ formatDate(date) }}</div>
  <button @click="showMessage('Hello')">点击</button>
</template>

10. injectAtEnd - 注入位置

作用: 控制自动导入语句的注入位置。

案例 10.1:在文件末尾注入
AutoImport({
  injectAtEnd: true, // 在文件末尾注入 import(默认 false,在文件开头)
})

完整配置示例

Vite 配置示例

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import path from 'path'

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      // 1. 自动导入库的 API
      imports: [
        'vue',
        'vue-router',
        'pinia',
        {
          '@vueuse/core': ['useMouse', 'useFetch'],
          'lodash-es': ['debounce', 'throttle'],
        },
      ],
      
      // 2. 自动导入自定义目录
      dirs: [
        './src/utils/**',
        './src/composables/**',
      ],
      
      // 3. 动态解析器
      resolvers: [
        ElementPlusResolver({
          importStyle: 'sass',
        }),
        // 自定义 resolver
        (name) => {
          if (name.startsWith('use')) {
            return { name, from: `@/composables/${name}` }
          }
        },
      ],
      
      // 4. 包含的文件
      include: [
        /\.[tj]sx?$/,
        /\.vue$/,
        /\.vue\?vue/,
      ],
      
      // 5. 排除的文件
      exclude: [
        /node_modules/,
        /\.test\.[tj]sx?$/,
        /\.spec\.[tj]sx?$/,
      ],
      
      // 6. 类型声明文件
      dts: './src/auto-imports.d.ts',
      
      // 7. ESLint 配置
      eslintrc: {
        enabled: true,
        filepath: './.eslintrc-auto-import.json',
        globalsPropValue: true,
      },
      
      // 8. 按文件名默认导出
      defaultExportByFilename: false,
      
      // 9. Vue 模板支持(实验性)
      vueTemplate: false,
      
      // 10. 注入位置
      injectAtEnd: false,
    }),
  ],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
})

Webpack 配置示例

const AutoImport = require('unplugin-auto-import/webpack')

module.exports = {
  plugins: [
    AutoImport({
      imports: ['vue', 'vue-router'],
      dts: true,
    }),
  ],
}

配置选项总结表

选项 类型 默认值 作用 常用值
imports Array<String | Object> [] 自动导入库的 API ['vue', 'vue-router']
dirs Array<String | Object> [] 自动导入自定义目录 ['./src/utils/**']
resolvers Array<Function> [] 动态解析标识符来源 [ElementPlusResolver()]
include Array<Regex | String> [/\.vue$/, /\.[tj]sx?$/] 包含的文件模式 [/\.vue$/, /\.[tj]sx?$/]
exclude Array<Regex | String> [/node_modules/] 排除的文件模式 [/node_modules/]
dts Boolean | String false 类型声明文件 true'./src/auto-imports.d.ts'
eslintrc Object { enabled: false } ESLint 配置 { enabled: true }
defaultExportByFilename Boolean false 按文件名导入 true
vueTemplate Boolean false 模板支持 false
injectAtEnd Boolean false 注入位置 false

常见问题

Q1: 为什么我的自定义函数没有被自动导入?

A: 检查以下几点:

  1. 确保函数是命名导出export constexport function),不是默认导出
  2. 检查 dirs 配置的路径是否正确
  3. 确保文件在 include 范围内,不在 exclude 范围内
  4. 如果使用默认导出,启用 defaultExportByFilename: true

Q2: TypeScript 类型提示不工作?

A:

  1. 确保 dts 配置已启用
  2. tsconfig.jsoninclude 中添加生成的类型声明文件
  3. 重启 IDE/编辑器

Q3: ESLint 报 no-undef 错误?

A:

  1. 启用 eslintrc.enabled: true
  2. .eslintrc.js 中引入生成的配置文件
  3. 重启 ESLint 服务

Q4: 如何只导入部分 API?

A: 使用对象形式的 imports 配置:

imports: [
  {
    'vue': ['ref', 'computed'], // 只导入这两个
  },
]

Q5: 如何排除某些文件不被处理?

A: 使用 exclude 配置:

exclude: [
  /\.test\.[tj]sx?$/,  // 排除测试文件
  /node_modules/,      // 排除 node_modules
]

Q6: 自定义工具函数如何自动导入?

A:

  1. 将函数改为命名导出
  2. 配置 dirs 指向函数所在目录
  3. 使用 Glob 模式支持子目录:'./src/utils/**'

最佳实践

  1. 命名导出优先:自定义函数使用命名导出,便于自动导入
  2. 类型声明文件:始终启用 dts,提供类型提示
  3. ESLint 配置:启用 eslintrc,避免 no-undef 错误
  4. 精确导入:使用对象形式的 imports,只导入需要的 API
  5. 目录结构:合理组织 utilscomposables 等目录
  6. 性能优化:使用 includeexclude 限制处理范围

相关链接


更新日志

  • v0.16.0+: 支持 defaultExportByFilename 选项
  • v0.15.0+: 改进 dirs 的 Glob 模式支持
  • v0.10.0+: 添加 vueTemplate 实验性功能

文档版本: 1.0.0
最后更新: 2024年

关于ruyi生产正常,开发异常的问题记录

问题描述

因公司需要,使用的环境是ruyi的前端部分,采用vue2+element-ui

1.当以开发环境进入页面时,第一次正常,在刷新后会白屏,或者卡在加载页面,控制台抛出错误 2.关于样式异常问题

Uncaught SyntaxError: Unexpected token '<'

但进入生产环境访问,一切正常,刷新后也依然正常。

原因

问题就出在Uncaught SyntaxError上,这是js的语法错误。例如

const 

上面代码会抛出

/Users/zhutao/code/test-code/esm.cjs:4
const 
      

SyntaxError: Unexpected end of input

当点击抛出部分进入具体错误地方,会进入html页面,错误就出在这。

graph TD
浏览器请求获取html页面 --> 浏览器解析html -->解析script与link标签

问题就出在解析解析script与link标签上

总所周知道script的标签使用

<script src="xxxx">

这时候浏览器发请求拿取到js问题,但如果拿取到的并非js文件,那自然会有SyntaxError错,本人就遇到这种情况。生产正常的原因在于ng配置对了,开发环境配置错了。因为开发环境也有类似于 try_files uriuri uri/ /xxx/index.html;的配置逻辑,既无论请求什么如果请求不到了就返回/xxx/index.html 但js文件和css文件是实际存在的,是请求的到的,理论上是不应该返回html

本人具体前端错误配置了啥忘了,因为vue-cli在配置文件中需要重启才生效,

export default new Router({
  mode: 'history', // 去掉url中的#
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes,
  base: '/yyy', // 当路由配置base 到时候进入http://xxx:xx/yyy才会展示页面。http://xxx:xx会空白
  
  
  
  对应的vite/vue-cli都得配置
  publicPath: process.env.NODE_ENV === "production" ? "/yyy/" : "/",
  既生产环境需要添加nbbz 开发环境不需要添加,如果生产不编写/yyy/会导致生产上有问题
})

关于样式问题

在ruyi样式问题当中/srccomponents/ThemePicker 这个文件当中的有个setTheme函数,当中的

   if (!this.chalk) {
        const url = `${process.env.VUE_APP_BASE_URL}/styles/theme-chalk/index.css`
        await this.getCSSString(url, 'chalk')
      }
生产
VUE_APP_BASE_URL = '/yyy'
开发
VUE_APP_BASE_URL = 'http://localhost:8090'

styles/theme-chalk/index.css这是手动请求,不是浏览器请求的css。文件文件在public/styles下面 当以上路由在生产当中配置了base 请求头前面也需要带上,否则会报404

而开发环境需要带http://localhost:服务端口 。因为开发的时候没有自动携带前面的ip

20 周年之际!jQuery 4.0 正式发布!轻装上阵

2006 年 1 月 14 日,John Resig 发布了名为 jQuery 的 JavaScript 库。

至今已经整整过去了 20 年!

你还记得第一次在项目中使用 jQuery 的场景吗?

在那个浏览器兼容性让人头疼,DOM 操作繁琐复杂的时代,jQuery 凭借着“Write less, do more”的理念,几乎是那个时代网站开发的标配。

后来,前端框架层出不穷,React、Vue、Angular 各领风骚,但 jQuery 依然在全球数百万网站上默默工作着。

如今, jQuery 4.0.0 正式版发布。

这也是 jQuery 近 10 年来的首个主要版本,标志着 jQuery 正式踏上了现代化转型之路。

让我们一起来看看 jQuery 4.0 都做了哪些更新。

1. 彻底告别 IE

jQuery 4.0 不再支持 IE 10 及更早版本。IE 11 预计在 5.0 版本移除。

同时也停止了一些老旧浏览器的支持,这使得 jQuery 代码更清爽,文件体积更小,性能提升显著。

2. 安全大升级

jQuery 4.0 引入了对 Trusted Types 的支持,jQuery 内部会自动通过 TrustedHTML 封装字符串,避免被 CSP 拦截,大大降低了网站被黑客攻击的风险。

3. 架构现代化

jQuery 源码从 AMD 迁移到了 ES Modules,这意味着更好的模块化开发体验,并为未来拆分功能打下基础。

4. API 精简

jQuery 4.0 移除了 15 个废弃的 API,这些函数要么是内部使用,要么已经有了原生的替代方案。

5. jQuery 的全新定位

有人可能会问:现在前端框架这么发达,jQuery 还有存在的必要吗?

答案是肯定的!

jQuery 不是要重新成为前端主角,而是在它适应的场景中继续发光发热。

6. 最后

jQuery 4.0 不是一次“重生”,而是一次面向现代 Web 的断舍离。它抛弃了历史包袱,拥抱了安全标准,清理了冗余代码,做了工程化升级。

20 年前,jQuery 改变了 Web 开发的方式;20 年后,它选择了与时俱进。

对于用过 jQuery 的老程序员来说,虽然我们已经习惯了 Vue、React 的思维模式,但看到 jQuery 的这次蜕变,依然会心潮澎湃。

因为这是我们青春岁月里最美好的代码记忆。

我整理了一份 Vue 性能优化指南(给AI用的)

为什么做这个

说实话,这个项目是我自己用的。

工作这几年,遇到的性能问题基本都是类似的坑:接口瀑布流、bundle 越来越大、响应式乱用。每次踩完坑修好了,过段时间换个项目又踩一遍。

后来想着,干脆整理一份文档,自己查方便,也能给 AI 编码助手看(我现在用 Claude Code),这样审代码的时候能提前发现问题。

整理完发现,好像也可以分享出来,说不定有人也遇到过这些问题。

我踩过的坑

这几年写 Vue 项目(Vue 2/3 + Nuxt 都有),踩过不少坑:

接口请求变成瀑布流 一个 await 接一个 await,明明能并行的请求硬是串行了。用户抱怨页面慢,一查发现 3 个接口排队等了 750ms。

bundle 体积失控 每次加需求就往里塞代码,没人关心打包结果。等到首屏白屏 3 秒了,才发现 JavaScript 已经 300KB+。

响应式系统滥用 大对象直接 ref(),上千条商品数据,每个字段都变成响应式。渲染一卡一卡的,还以为是组件写得不好。

这些问题不是什么高深的优化,就是基本功。但忙起来就容易忽略,等出问题再改成本就高了。

怎么说呢,优化要分轻重

我发现很多人(包括以前的我)做性能优化会搞错重点。

举个例子:页面有 600ms 的请求等待时间,结果花一周优化 computed 缓存。首屏加载了 300KB 的 JavaScript,结果去优化循环少跑几次。

其实应该先解决大问题:

  1. 先干掉请求瀑布流 - 能并行就并行,该预加载就预加载
  2. 再砍 bundle 体积 - 代码分割、动态导入、tree-shaking
  3. 然后才是组件和响应式优化 - 减少不必要的渲染

我按这个思路把规则分成了 10 个类别,从 CRITICALLOW,总共 46 条。先把影响大的问题解决了,那些微优化可以慢慢来。

里面有什么

10 个类别,46 条规则:

  • 消除异步瀑布流(CRITICAL)
  • 包体积优化(CRITICAL)
  • 服务端性能(HIGH)
  • 客户端数据获取(HIGH)
  • 响应式系统优化(MEDIUM-HIGH)
  • 渲染性能(MEDIUM)
  • Vue 2 特定优化(MEDIUM)
  • Vue 3 特定优化(MEDIUM)
  • JavaScript 性能(LOW-MEDIUM)
  • 高级模式(LOW)

每条规则的格式:

  • 影响等级(CRITICAL / HIGH / MEDIUM / LOW)
  • 错误示例(我以前写过的错误代码)
  • 正确示例(后来改成什么样)
  • Vue 2/3 兼容性说明

举几个我踩过的坑

坑 1:不需要的 await 也在阻塞代码

以前写过这样的代码:

async function handleRequest(userId: string, skipProcessing: boolean) {
  // 即使 skipProcessing=true,也会等待 userData
  const userData = await fetchUserData(userId)

  if (skipProcessing) {
    // 立即返回,但前面已经浪费时间等待了
    return { skipped: true }
  }

  // 只有这个分支使用 userData
  return processUserData(userData)
}

问题是,即使 skipProcessing=true,还是会去请求 userData。白白浪费时间。

后来改成这样:

async function handleRequest(userId: string, skipProcessing: boolean) {
  if (skipProcessing) {
    return { skipped: true }
  }

  // 只在需要时才获取数据
  const userData = await fetchUserData(userId)
  return processUserData(userData)
}

其实很简单,但之前就是没注意到。

坑 2:大对象别直接用 ref

1000 条商品数据,每条 10+ 个字段,以前直接 ref()

<script setup lang="ts">
import { ref } from 'vue'

// 1000 个商品,每个商品 10+ 字段,全部变成响应式
const products = ref<Product[]>([])

async function loadProducts() {
  products.value = await fetchProducts()
  // Vue 会递归遍历所有对象,添加响应式代理
}
</script>

渲染的时候卡得要命。后来发现应该用 shallowRef

<script setup lang="ts">
import { shallowRef } from 'vue'

// 只有数组本身是响应式的,内部对象保持普通对象
const products = shallowRef<Product[]>([])

async function loadProducts() {
  // 替换整个数组触发更新,无需深度响应式
  products.value = await fetchProducts()
}
</script>

shallowRef 只让数组本身响应式,内部对象保持普通对象。更新时替换整个数组就能触发响应,省了大量性能开销。

几个真实案例(我遇到过的)

案例 1:别对同一个数组循环多次

之前接手一个项目,发现同一个商品列表循环了 5 次:

// 错误:5 次独立遍历
const discounted = products.filter(p => p.discount > 0)
const inStock = products.filter(p => p.stock > 0)
const featured = products.filter(p => p.featured)
const totalValue = products.reduce((sum, p) => sum + p.price, 0)
const avgPrice = totalValue / products.length

看着就难受。后来改成一次循环:

// 正确:一次遍历
const stats = products.reduce((acc, product) => {
  if (product.discount > 0) acc.discounted.push(product)
  if (product.stock > 0) acc.inStock.push(product)
  if (product.featured) acc.featured.push(product)
  acc.totalValue += product.price
  return acc
}, { discounted: [], inStock: [], featured: [], totalValue: 0 })

const avgPrice = stats.totalValue / products.length

商品少的时候看不出来,数据一多性能差距就很明显了。

案例 2:独立的请求不要排队

用户详情页,三个互不依赖的接口,结果在串行调用:

// 错误:串行:总耗时 = 300ms + 200ms + 250ms = 750ms
const user = await fetchUser(userId)          // 300ms
const posts = await fetchUserPosts(userId)    // 200ms
const comments = await fetchUserComments(userId) // 250ms

改成并行之后:

// 正确:并行:总耗时 = max(300ms, 200ms, 250ms) = 300ms
const [user, posts, comments] = await Promise.all([
  fetchUser(userId),
  fetchUserPosts(userId),
  fetchUserComments(userId)
])

总耗时从 750ms 降到 300ms,页面快了一半多。这种优化投入产出比最高。

案例 3:长列表用 CSS content-visibility

1000+ 条评论的页面,初始渲染很慢:

<!-- 错误:所有评论立即渲染 -->
<div v-for="comment in comments" :key="comment.id">
  <CommentCard :comment="comment" />
</div>

后来加上 content-visibility

<!-- 正确:浏览器跳过屏幕外的渲染 -->
<div
  v-for="comment in comments"
  :key="comment.id"
  class="comment-item"
>
  <CommentCard :comment="comment" />
</div>

<style>
.comment-item {
  content-visibility: auto;
  contain-intrinsic-size: auto 200px;
}
</style>

浏览器会跳过屏幕外的渲染,初始加载快了 5-10 倍,滚动也流畅多了。这个 CSS 属性真的好用。

怎么用

直接看

# 克隆仓库
git clone https://github.com/ursazoo/vue-best-practices.git

# 安装依赖
npm install

# 构建 AGENTS.md
npm run build

克隆下来,直接看 rules/ 目录下的规则文件。每个文件都是独立的,包含问题说明、代码示例和解决方案。

也可以看构建好的 AGENTS.md,把所有规则整合在一起,方便搜索。

集成到 AI 编码助手

如果你也在用 Claude Code、Cursor 这类 AI 工具写代码,可以集成进去:

npx add-skill vue-best-practices

AI 审查代码的时候,如果发现性能问题(比如请求瀑布流、过度响应式),会参考这些规则给出优化建议。我现在就是这么用的,挺方便。

Vue 2 还是 Vue 3

都支持。每条规则都标注了版本兼容性:

  • Vue 2 & 3 通用:基础的性能优化技巧
  • Vue 3 Only:用了 <script setup>shallowRefSuspense 等新特性
  • Vue 2 Only:针对 Vue 2 的特定优化(比如 Object.freeze()

老项目也能用,新项目能用得更充分。

项目地址

GitHub: github.com/ursazoo/vue…

欢迎贡献

这是个开源项目。如果你在生产环境踩过坑、有更好的优化方案,欢迎提 Issue 或 PR。

特别是:

  • 实际项目中遇到的性能问题
  • 现有规则的改进建议
  • Vue/Nuxt 新版本的优化技巧

项目信息

  • 46 条规则,10 个类别
  • 按影响程度排序(先解决大问题)
  • 支持 Vue 2/3 和 Nuxt
  • 适配 AI 编码助手

希望对你有帮助。

前端实现元素叠加

在前端开发中实现元素叠加(Overlapping)是一个非常常见的需求,从简单的文字盖在图片上,到复杂的层叠动画。实现这一效果的方式多种多样,每种方式都有其适用的场景、优缺点以及对布局流的影响。 实现

webpack的工作原理

Webpack 是一个**模块打包器**,核心工作是把项目中散落的、各种格式的模块(JS/TS/CSS/ 图片等),按照依赖关系打包成浏览器能识别的静态资源。
❌