Vue 3.2发布后,组合式API、<script setup>、Vite等新特性逐渐成熟,Vue 3的生态也越来越完善。

我用Vue 3做了几个项目,从一开始的不适应,到后来的得心应手,踩了不少坑,也积累了一些经验。本文总结Vue 3.2+生态的最佳实践,包括项目搭建、代码组织、状态管理、路由、UI组件库、性能优化、工程化等方面。

希望能帮你搭建一个现代化、高性能、易维护的Vue 3项目。

一、项目搭建

项目搭建是第一步,选对工具和配置,后面开发会顺利很多。

1. 用Vite,不要用Vue CLI

Vue 3.2+时代,首选Vite作为构建工具。

Vite的优势:

  • 启动快:基于ES Module,冷启动毫秒级
  • 热更新快:HMR速度极快,改代码秒级更新
  • 配置简单:比webpack简单很多
  • 生态好:Vue官方推荐,插件丰富

Vue CLI已经进入维护模式,新项目不要再用了。

创建项目:

npm create vite@latest my-vue-app -- --template vue

或者用Vue官方的create-vue:

npm create vue@latest

create-vue会让你选择是否需要TypeScript、Router、Pinia、Vitest等,更方便。

2. 用TypeScript

Vue 3.2+对TypeScript的支持非常好,建议新项目都用TypeScript。

好处:

  • 类型安全,减少bug
  • 代码提示更好,开发效率高
  • 重构更安全
  • 团队协作更顺畅

<script setup>中用TypeScript非常方便:

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

interface User {
  id: number
  name: string
}

const user = ref<User | null>(null)
const userName = computed(() => user.value?.name ?? '未登录')
</script>

3. 目录结构

推荐的目录结构:

src/
├── assets/          # 静态资源(图片、字体等)
├── components/      # 通用组件
├── composables/     # 组合式函数(hooks)
├── layouts/         # 布局组件
├── router/          # 路由配置
├── stores/          # 状态管理(Pinia)
├── styles/          # 全局样式
├── utils/           # 工具函数
├── views/           # 页面组件
├── App.vue
└── main.ts

这个结构清晰,职责分明,团队协作方便。

二、组合式API和<script setup>

Vue 3.2+的核心是组合式API和<script setup>

1. 用<script setup>,不要用普通的setup函数

<script setup>是Vue 3.2的语法糖,比普通的setup函数更简洁。

<!-- 推荐 -->
<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)
function increment() {
  count.value++
}
</script>

<!-- 不推荐 -->
<script lang="ts">
import { ref, defineComponent } from 'vue'

export default defineComponent({
  setup() {
    const count = ref(0)
    function increment() {
      count.value++
    }
    return { count, increment }
  }
})
</script>

<script setup>的优势:

  • 代码更简洁,不需要return
  • 顶层的变量和函数自动暴露给模板
  • 更好的TypeScript支持
  • 更好的运行时性能

2. 用组合式函数(Composables)复用逻辑

组合式函数是Vue 3复用逻辑的标准方式,相当于Vue 2的mixin,但更清晰、更安全。

例子:useMouse

// composables/useMouse.ts
import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function update(event: MouseEvent) {
    x.value = event.pageX
    y.value = event.pageY
  }

  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  return { x, y }
}

使用:

<script setup>
import { useMouse } from '@/composables/useMouse'

const { x, y } = useMouse()
</script>

组合式函数的命名约定:以use开头,放在composables/目录下。

3. 合理拆分组件和组合式函数

  • 组件:关注UI和交互,模板和样式
  • 组合式函数:关注逻辑和状态,可以在多个组件中复用

不要把所有逻辑都写在组件里,也不要把UI逻辑放到组合式函数里。合理拆分,代码更清晰。

三、状态管理:用Pinia,不要用Vuex

Vue 3.2+时代,状态管理用Pinia,不要用Vuex。

Pinia是Vue官方推荐的新状态管理库,比Vuex更简单、更好用。

1. Pinia的优势

  • API更简单,没有mutations,直接修改state
  • 更好的TypeScript支持
  • 支持组合式API
  • 更轻量,体积更小
  • Vue官方推荐,Vuex 5就是Pinia

2. 定义Store

// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  // state
  const name = ref('')
  const age = ref(0)

  // getters
  const isAdult = computed(() => age.value >= 18)

  // actions
  function setUser(newName: string, newAge: number) {
    name.value = newName
    age.value = newAge
  }

  return { name, age, isAdult, setUser }
})

3. 使用Store

<script setup>
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'

const userStore = useUserStore()

// 用storeToRefs保持响应式
const { name, age, isAdult } = storeToRefs(userStore)

// 直接调用action
userStore.setUser('张三', 25)
</script>

注意:从store中解构ref,要用storeToRefs,否则会失去响应式。

四、路由:Vue Router 4

Vue 3用Vue Router 4,和Vue 2的Vue Router 3用法差不多,但有一些变化。

1. 基本配置

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/views/About.vue')
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

2. 路由懒加载

用动态import实现路由懒加载,减小首屏包体积:

component: () => import('@/views/Home.vue')

3. 路由守卫

router.beforeEach((to, from, next) => {
  // 检查登录状态
  const isLoggedIn = checkAuth()
  if (to.meta.requiresAuth && !isLoggedIn) {
    next('/login')
  } else {
    next()
  }
})

4. 在组合式API中使用路由

<script setup>
import { useRouter, useRoute } from 'vue-router'

const router = useRouter()
const route = useRoute()

function goToAbout() {
  router.push('/about')
}
</script>

五、UI组件库选择

Vue 3的UI组件库已经很丰富了,根据项目需求选择。

1. Element Plus

  • 最流行的Vue 3组件库,Element UI的Vue 3版本
  • 组件丰富,文档完善
  • 适合后台管理系统
  • 支持按需引入

2. Ant Design Vue

  • 蚂蚁金服的设计体系,Vue 3版本
  • 设计规范,组件丰富
  • 适合企业级应用
  • 支持按需引入

3. Naive UI

  • 比较新的Vue 3组件库,TypeScript优先
  • 主题定制方便,性能好
  • 组件丰富
  • 适合追求现代感的项目

4. Vant

  • 有赞的移动端组件库
  • 适合移动端H5和小程序
  • 组件丰富,文档完善

5. 选择建议

  • 后台管理系统:Element Plus或Ant Design Vue
  • 移动端H5:Vant
  • 追求现代感和性能:Naive UI
  • 特殊需求:可以自己封装组件,或用Tailwind CSS

六、样式方案

Vue 3.2+的样式方案,推荐以下几种。

1. Scoped CSS

Vue单文件组件的scoped样式,是最基本的方案:

<style scoped>
.title {
  font-size: 20px;
  color: #333;
}
</style>

scoped样式只会作用于当前组件,不会污染全局。

2. CSS变量

用CSS变量做主题定制:

<style>
:root {
  --primary-color: #409eff;
}

.button {
  background-color: var(--primary-color);
}
</style>

3. Tailwind CSS

如果项目需要快速开发、高度定制,可以用Tailwind CSS。

Tailwind是原子化CSS框架,用类名组合样式,不需要写CSS文件。

<template>
  <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
    按钮
  </button>
</template>

Tailwind和Vue配合很好,Vite也有官方插件。

4. CSS预处理器

可以用Sass/Less/Stylus,但Vite已经内置支持,不需要额外配置太多。

建议:简单项目用scoped CSS + CSS变量,复杂项目用Tailwind CSS。

七、性能优化

Vue 3本身性能已经很好了,但还有一些优化点。

1. 合理使用v-if和v-show

  • v-if:条件不满足时不渲染,适合频繁切换的场景
  • v-show:始终渲染,只是切换display,适合不频繁切换的场景

不要在同一个元素上同时用v-if和v-for。

2. key的使用

v-for一定要加key,而且用稳定的key(比如id),不要用index。

<!-- 好 -->
<li v-for="item in list" :key="item.id">{{ item.name }}</li>

<!-- 不好 -->
<li v-for="(item, index) in list" :key="index">{{ item.name }}</li>

3. 计算属性和侦听器

  • 能用computed的,不要用watch
  • computed有缓存,性能更好
  • watch适合异步操作和副作用

4. 懒加载

  • 路由懒加载:动态import
  • 组件懒加载:defineAsyncComponent
  • 图片懒加载:用v-lazy或Intersection Observer

5. 虚拟列表

长列表(几百条以上)用虚拟列表,只渲染可视区域的内容。推荐用vue-virtual-scroller。

6. 打包优化

  • 用Vite的构建优化(自动代码分割、tree shaking)
  • 大依赖用动态import
  • 图片用WebP格式,压缩体积
  • 用CDN加载第三方库

八、工程化

工程化是项目质量的保障。

1. ESLint + Prettier

  • ESLint:代码规范检查
  • Prettier:代码格式化

配合Vite插件,保存时自动格式化。

2. Husky + lint-staged

  • Husky:Git hooks工具
  • lint-staged:只检查暂存区的文件

提交代码前自动运行ESLint和Prettier,保证代码质量。

3. 单元测试

用Vitest做单元测试,Vite原生支持,速度快。

// utils/__tests__/sum.test.ts
import { describe, it, expect } from 'vitest'
import { sum } from '../sum'

describe('sum', () => {
  it('adds two numbers', () => {
    expect(sum(1, 2)).toBe(3)
  })
})

4. 组件测试

用Vue Test Utils + Vitest做组件测试。

5. CI/CD

用GitHub Actions、GitLab CI等做持续集成,自动运行测试、构建、部署。

九、常见的坑

说说Vue 3.2+开发中常见的坑。

坑一:ref和reactive的选择

  • ref:适合基本类型,也可以用对象,但需要.value访问
  • reactive:适合对象,不需要.value,但不能直接替换整个对象

建议:基本类型用ref,复杂对象用reactive。但团队统一用ref也可以,更一致。

坑二:解构props失去响应式

<script setup>
const props = defineProps<{ user: User }>()

// 这样会失去响应式
const { user } = props

// 要用toRefs
import { toRefs } from 'vue'
const { user } = toRefs(props)
</script>

坑三:watch的 immediate 和 deep

  • immediate: true:立即执行一次
  • deep: true:深度监听对象内部变化

深度监听性能差,尽量避免。如果需要,考虑用computed或具体字段监听。

坑四:生命周期钩子的使用

<script setup>中,生命周期钩子要从vue导入:

<script setup>
import { onMounted, onUnmounted } from 'vue'

onMounted(() => {
  console.log('mounted')
})
</script>

坑五:defineProps和defineEmits

<script setup>中,用defineProps和defineEmits,不需要导入:

<script setup>
const props = defineProps<{ title: string }>()
const emit = defineEmits<{ (e: 'click'): void }>()
</script>

十、写在最后

Vue 3.2+的生态已经非常成熟了,Vite、Pinia、Vue Router 4、TypeScript、各种UI组件库,构成了一个现代化的前端开发体系。

本文总结了我在Vue 3.2+项目中的一些最佳实践,包括项目搭建、组合式API、状态管理、路由、UI组件库、样式、性能优化、工程化等。希望能帮你少走弯路。

当然,最佳实践不是一成不变的,要根据项目的实际情况选择合适的方案。技术在发展,最佳实践也会不断更新。

2022年了,Vue 3已经成为主流。如果你还在用Vue 2,建议尽快升级到Vue 3,体验组合式API和Vite带来的开发体验提升。

最后,用一句话总结:"Vue 3.2+的最佳实践,核心是用组合式API组织逻辑,用Pinia管理状态,用Vite构建,用TypeScript保证质量。"

祝大家的Vue项目都能又快又稳。