Vue2 与 Vue3 生态系统及工程化对比 - 面试宝典

一、TypeScript 支持

面试问题1:Vue3 的 TypeScript 支持相比 Vue2 有哪些改进?

详细解答:

Vue2 的 TypeScript 困境

1. Options API 的类型推导问题

typescript 复制代码
// Vue2 + TypeScript
import Vue from 'vue'

export default Vue.extend({
  data() {
    return {
      count: 0,
      message: 'Hello'
    }
  },
  
  computed: {
    // ❌ this 的类型推导困难
    doubleCount(): number {
      return this.count * 2  // TypeScript 很难推导 this.count 的类型
    }
  },
  
  methods: {
    // ❌ 参数和返回值类型需要手动标注
    increment(step: number): void {
      this.count += step
      // TypeScript 可能无法检测 this.count 是否存在
    },
    
    async fetchData(): Promise<void> {
      // ❌ this.$refs 的类型推导很困难
      const input = this.$refs.input as HTMLInputElement
      // 需要类型断言
    }
  },
  
  mounted() {
    // ❌ 无法推导 computed 的返回类型
    const doubled = this.doubleCount  // 类型是 any
  }
})

问题分析:

  1. this 上下文复杂

    • data、computed、methods 都挂载在 this 上
    • TypeScript 难以追踪这些属性的类型
  2. 运行时合并

    • mixins 的类型合并困难
    • r e f s 、 refs、 refs、attrs 等特殊属性类型推导不准确
  3. 装饰器方案的问题

typescript 复制代码
// vue-class-component (Vue2 的 TS 解决方案)
import { Vue, Component, Prop } from 'vue-property-decorator'

@Component
export class MyComponent extends Vue {
  @Prop({ type: String, required: true })
  message!: string  // 需要 ! 断言
  
  count: number = 0
  
  get doubleCount(): number {
    return this.count * 2
  }
  
  increment(): void {
    this.count++
  }
}

// 问题:
// 1. 需要额外的装饰器库
// 2. 与标准 Vue 语法不一致
// 3. 编译输出代码复杂
// 4. Stage 2 装饰器提案,不稳定

2. 类型定义文件复杂

typescript 复制代码
// Vue2 的类型定义需要复杂的声明合并
import Vue from 'vue'

declare module 'vue/types/vue' {
  interface Vue {
    $myPlugin: {
      doSomething(): void
    }
  }
}

declare module 'vue/types/options' {
  interface ComponentOptions<V extends Vue> {
    myOption?: string
  }
}

// 问题:
// 1. 需要理解 TypeScript 的声明合并
// 2. 模块路径容易写错
// 3. 维护困难
Vue3 的 TypeScript 优势

1. Composition API 的完美类型推导

typescript 复制代码
// Vue3 + TypeScript
import { ref, computed, onMounted } from 'vue'

export default {
  setup() {
    // ✅ 自动推导类型:Ref<number>
    const count = ref(0)
    
    // ✅ 自动推导类型:Ref<string>
    const message = ref('Hello')
    
    // ✅ 自动推导返回类型:ComputedRef<number>
    const doubleCount = computed(() => count.value * 2)
    
    // ✅ 参数和返回类型清晰
    const increment = (step: number): void => {
      count.value += step
    }
    
    onMounted(() => {
      // ✅ doubleCount 的类型自动推导为 number
      console.log(doubleCount.value)  // number 类型
    })
    
    return {
      count,        // Ref<number>
      message,      // Ref<string>
      doubleCount,  // ComputedRef<number>
      increment     // (step: number) => void
    }
  }
}

2. 复杂类型的完整支持

typescript 复制代码
// 定义复杂类型
interface User {
  id: number
  name: string
  email: string
  profile: {
    avatar: string
    bio: string
  }
}

interface ApiResponse<T> {
  data: T
  status: number
  message: string
}

export default {
  setup() {
    // ✅ 泛型支持完美
    const user = ref<User | null>(null)
    
    // ✅ 自动推导:ComputedRef<string>
    const userName = computed(() => user.value?.name ?? 'Guest')
    
    // ✅ 异步函数类型推导
    const fetchUser = async (id: number): Promise<ApiResponse<User>> => {
      const response = await fetch(`/api/users/${id}`)
      return response.json()
    }
    
    // ✅ 类型守卫
    const isLoggedIn = computed(() => user.value !== null)
    
    return {
      user,         // Ref<User | null>
      userName,     // ComputedRef<string>
      fetchUser,    // (id: number) => Promise<ApiResponse<User>>
      isLoggedIn    // ComputedRef<boolean>
    }
  }
}

3. 组合函数的类型复用

typescript 复制代码
// composables/useUser.ts
import { ref, computed, Ref, ComputedRef } from 'vue'

interface User {
  id: number
  name: string
}

interface UseUserReturn {
  user: Ref<User | null>
  isLoggedIn: ComputedRef<boolean>
  login: (username: string, password: string) => Promise<void>
  logout: () => void
}

// ✅ 返回类型清晰明确
export function useUser(): UseUserReturn {
  const user = ref<User | null>(null)
  
  const isLoggedIn = computed(() => user.value !== null)
  
  const login = async (username: string, password: string): Promise<void> => {
    // 登录逻辑
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ username, password })
    })
    user.value = await response.json()
  }
  
  const logout = (): void => {
    user.value = null
  }
  
  return {
    user,
    isLoggedIn,
    login,
    logout
  }
}

// 使用时完美的类型提示
import { useUser } from './composables/useUser'

export default {
  setup() {
    const { user, isLoggedIn, login, logout } = useUser()
    
    // ✅ TypeScript 知道所有类型
    // user: Ref<User | null>
    // isLoggedIn: ComputedRef<boolean>
    // login: (username: string, password: string) => Promise<void>
    // logout: () => void
    
    return { user, isLoggedIn, login, logout }
  }
}

4. Props 和 Emits 的类型定义

typescript 复制代码
// Vue2 - 类型定义困难
export default Vue.extend({
  props: {
    message: String,
    count: Number
  },
  // ❌ TypeScript 无法准确推导 this.message 和 this.count
})

// Vue3 - 完美的类型推导
import { defineComponent, PropType } from 'vue'

interface Post {
  id: number
  title: string
  content: string
}

export default defineComponent({
  props: {
    // ✅ 简单类型
    message: {
      type: String,
      required: true
    },
    count: {
      type: Number,
      default: 0
    },
    
    // ✅ 复杂类型
    post: {
      type: Object as PropType<Post>,
      required: true
    },
    
    // ✅ 数组类型
    tags: {
      type: Array as PropType<string[]>,
      default: () => []
    },
    
    // ✅ 函数类型
    onClick: {
      type: Function as PropType<(id: number) => void>,
      required: false
    }
  },
  
  emits: {
    // ✅ 定义 emit 的类型
    update: (value: string) => typeof value === 'string',
    delete: (id: number) => typeof id === 'number'
  },
  
  setup(props, { emit }) {
    // ✅ props 类型完全推导
    console.log(props.message)  // string
    console.log(props.count)    // number
    console.log(props.post.title)  // string
    
    // ✅ emit 类型检查
    emit('update', 'new value')  // ✅ 正确
    emit('update', 123)          // ❌ 类型错误
    emit('delete', 1)            // ✅ 正确
    emit('delete', '1')          // ❌ 类型错误
  }
})

使用 <script setup> 语法糖:

typescript 复制代码
<script setup lang="ts">
import { ref, computed } from 'vue'

interface Post {
  id: number
  title: string
}

// ✅ Props 类型定义
interface Props {
  message: string
  count?: number
  post: Post
  tags?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  count: 0,
  tags: () => []
})

// ✅ Emits 类型定义
interface Emits {
  (e: 'update', value: string): void
  (e: 'delete', id: number): void
}

const emit = defineEmits<Emits>()

// ✅ 完美的类型推导
const localCount = ref(props.count)  // Ref<number>
const doubled = computed(() => localCount.value * 2)  // ComputedRef<number>

function handleUpdate() {
  emit('update', 'new value')  // ✅ 类型检查
}
</script>

5. Ref 解包的智能类型

typescript 复制代码
import { ref, reactive } from 'vue'

const count = ref(0)
const user = reactive({
  name: 'John',
  age: ref(25)  // ref 在 reactive 中自动解包
})

// ✅ 智能类型推导
console.log(count.value)  // 需要 .value
console.log(user.age)     // 自动解包,不需要 .value

// 类型系统知道:
// count: Ref<number>
// user: { name: string; age: number }  // age 自动解包
实际开发对比

场景 1:表单处理

typescript 复制代码
// Vue2 + TypeScript
import Vue from 'vue'

interface FormData {
  username: string
  email: string
  age: number
}

export default Vue.extend({
  data(): { form: FormData; errors: Record<string, string> } {
    return {
      form: {
        username: '',
        email: '',
        age: 0
      },
      errors: {}
    }
  },
  
  methods: {
    // ❌ 参数类型需要重复定义
    validate(field: keyof FormData): boolean {
      // this.form 的类型推导不准确
      return true
    },
    
    // ❌ 返回类型推导困难
    async submit() {
      // ...
    }
  }
})

// Vue3 + TypeScript
import { ref, reactive } from 'vue'

interface FormData {
  username: string
  email: string
  age: number
}

export default {
  setup() {
    // ✅ 类型自动推导
    const form = reactive<FormData>({
      username: '',
      email: '',
      age: 0
    })
    
    const errors = ref<Record<string, string>>({})
    
    // ✅ 参数和返回类型清晰
    const validate = (field: keyof FormData): boolean => {
      // form 类型完全推导
      return true
    }
    
    // ✅ 异步函数类型明确
    const submit = async (): Promise<void> => {
      if (!validate('username')) return
      // ...
    }
    
    return {
      form,
      errors,
      validate,
      submit
    }
  }
}

场景 2:列表管理

typescript 复制代码
// Vue3 的优势
interface Todo {
  id: number
  text: string
  completed: boolean
}

export default {
  setup() {
    const todos = ref<Todo[]>([])
    const filter = ref<'all' | 'active' | 'completed'>('all')
    
    // ✅ 完美的类型推导和检查
    const filteredTodos = computed(() => {
      switch (filter.value) {
        case 'active':
          return todos.value.filter(t => !t.completed)
        case 'completed':
          return todos.value.filter(t => t.completed)
        default:
          return todos.value
      }
    })
    
    const addTodo = (text: string): void => {
      todos.value.push({
        id: Date.now(),
        text,
        completed: false
      })
    }
    
    const toggleTodo = (id: number): void => {
      const todo = todos.value.find(t => t.id === id)
      if (todo) {
        todo.completed = !todo.completed
      }
    }
    
    return {
      todos,           // Ref<Todo[]>
      filter,          // Ref<'all' | 'active' | 'completed'>
      filteredTodos,   // ComputedRef<Todo[]>
      addTodo,         // (text: string) => void
      toggleTodo       // (id: number) => void
    }
  }
}
TypeScript 支持对比总结
特性 Vue2 Vue3
类型推导 困难,需要大量手动标注 自动,几乎无需标注
this 上下文 类型推导不准确 无 this,类型清晰
Props 类型 需要 PropType 辅助 完美支持泛型
Emits 类型 不支持 完整类型检查
组合复用 mixins 类型合并困难 组合函数类型完整
IDE 支持 提示不准确 完美的智能提示
学习成本 需要学装饰器或复杂技巧 符合直觉

二、构建工具和开发体验

面试问题2:Vue3 在构建工具和开发体验上有哪些改进?

详细解答:

Vue2 的构建配置

1. Webpack 配置复杂

javascript 复制代码
// vue.config.js (Vue CLI 3/4)
module.exports = {
  // 需要理解 Webpack 配置
  configureWebpack: {
    resolve: {
      alias: {
        '@': path.resolve(__dirname, 'src')
      }
    },
    plugins: [
      new SomePlugin()
    ]
  },
  
  // 链式 API
  chainWebpack: config => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap(options => {
        // 修改 vue-loader 配置
        return options
      })
  },
  
  // CSS 相关配置
  css: {
    loaderOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`
      }
    }
  },
  
  // 开发服务器
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true
      }
    }
  }
}

问题:

  • 配置复杂,学习成本高
  • Webpack 打包速度慢(大项目需要几分钟)
  • HMR(热模块替换)在大项目中慢

2. 开发服务器启动慢

bash 复制代码
# Vue2 + Webpack
$ npm run serve

# 启动过程:
# 1. 分析所有模块依赖
# 2. 编译所有文件
# 3. 打包成 bundle
# 4. 启动开发服务器

# 大项目启动时间:30-60 秒甚至更长
Vue3 + Vite 的优势

1. 零配置,开箱即用

javascript 复制代码
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  
  // 简洁的配置
  resolve: {
    alias: {
      '@': '/src'
    }
  },
  
  server: {
    proxy: {
      '/api': 'http://localhost:3000'
    }
  }
})

// 大部分情况下,甚至可以零配置!

2. 极速的冷启动

bash 复制代码
# Vue3 + Vite
$ npm run dev

# 启动过程:
# 1. 启动开发服务器(几乎瞬间)
# 2. 按需编译(只编译当前访问的页面)

# 启动时间:1-2 秒(无论项目大小)

原理对比:

复制代码
Webpack (Bundle-based)
┌─────────────────────────────────────────┐
│ Entry                                   │
│   ↓                                     │
│ [分析依赖树]                              │
│   ↓                                     │
│ [编译所有模块] ← 耗时!                    │
│   ↓                                     │
│ [打包成 Bundle]                          │
│   ↓                                     │
│ Ready (30-60s)                          │
└─────────────────────────────────────────┘

Vite (Native ESM)
┌─────────────────────────────────────────┐
│ 启动服务器 (1-2s)                         │
│   ↓                                     │
│ 浏览器请求 /main.js                       │
│   ↓                                     │
│ [按需编译 main.js] ← 快!                 │
│   ↓                                     │
│ 浏览器请求依赖模块                         │
│   ↓                                     │
│ [按需编译依赖] ← 只编译需要的!             │
│   ↓                                     │
│ Ready                                   │
└─────────────────────────────────────────┘

3. 极速的热更新(HMR)

typescript 复制代码
// Webpack HMR
// 修改文件 -> 重新编译模块 -> 重新打包 -> 推送更新
// 大项目更新时间:2-5 秒

// Vite HMR
// 修改文件 -> 编译单个模块 -> 推送更新
// 更新时间:几乎瞬间(<100ms)

// Vite 的精准 HMR
<script setup>
import { ref } from 'vue'

// 修改这里
const count = ref(0)  // 改成 ref(100)

// Vite 只更新这个模块,组件状态保持
// count 的值会更新,但其他状态(如定时器、网络请求)不受影响
</script>

性能对比:

操作 Webpack (大项目) Vite
冷启动 30-60s 1-2s
HMR 2-5s <100ms
生产构建 60-120s 30-60s

4. 现代化的插件系统

typescript 复制代码
// Vite 插件(基于 Rollup)
import { Plugin } from 'vite'

// 创建一个简单的插件
function myPlugin(): Plugin {
  return {
    name: 'my-plugin',
    
    // 转换文件
    transform(code, id) {
      if (id.endsWith('.custom')) {
        return {
          code: processCustomFile(code),
          map: null
        }
      }
    },
    
    // 自定义服务器中间件
    configureServer(server) {
      server.middlewares.use((req, res, next) => {
        // 自定义请求处理
        next()
      })
    }
  }
}

// 使用插件
export default {
  plugins: [
    vue(),
    myPlugin()
  ]
}

常用插件生态:

typescript 复制代码
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'

export default defineConfig({
  plugins: [
    vue(),
    
    // JSX 支持
    vueJsx(),
    
    // 自动导入 Vue API
    AutoImport({
      imports: ['vue', 'vue-router', 'pinia']
    }),
    
    // 自动导入组件
    Components({
      // 自动导入 src/components 下的组件
      dirs: ['src/components']
    })
  ]
})

// 使用时无需手动导入
<script setup>
// ✅ 自动导入,无需 import
const count = ref(0)  // ref 自动导入
const router = useRouter()  // useRouter 自动导入
</script>

<template>
  <!-- ✅ 组件自动注册 -->
  <MyComponent />
</template>
<script setup> 语法糖

Vue2 写法:

vue 复制代码
<template>
  <div>
    <p>{{ message }}</p>
    <button @click="increment">{{ count }}</button>
  </div>
</template>

<script>
import { defineComponent, ref, computed, onMounted } from 'vue'
import MyComponent from './MyComponent.vue'

export default defineComponent({
  components: {
    MyComponent
  },
  
  props: {
    initialCount: {
      type: Number,
      default: 0
    }
  },
  
  setup(props, { emit }) {
    const count = ref(props.initialCount)
    const message = ref('Hello')
    
    const doubleCount = computed(() => count.value * 2)
    
    const increment = () => {
      count.value++
      emit('update', count.value)
    }
    
    onMounted(() => {
      console.log('mounted')
    })
    
    return {
      count,
      message,
      doubleCount,
      increment
    }
  }
})
</script>

Vue3 <script setup> 写法:

vue 复制代码
<template>
  <div>
    <p>{{ message }}</p>
    <button @click="increment">{{ count }}</button>
    <!-- ✅ 组件自动注册 -->
    <MyComponent />
  </div>
</template>

<script setup>
// ✅ 自动导入的组件
import MyComponent from './MyComponent.vue'

// ✅ 定义 props
const props = defineProps({
  initialCount: {
    type: Number,
    default: 0
  }
})

// ✅ 定义 emits
const emit = defineEmits(['update'])

// ✅ 直接声明响应式状态,自动暴露给模板
const count = ref(props.initialCount)
const message = ref('Hello')

// ✅ 直接声明计算属性
const doubleCount = computed(() => count.value * 2)

// ✅ 直接声明方法
const increment = () => {
  count.value++
  emit('update', count.value)
}

// ✅ 生命周期钩子
onMounted(() => {
  console.log('mounted')
})

// ✅ 不需要 return!所有顶层绑定自动暴露
</script>

优势:

  • 代码更简洁(减少 40% 代码量)
  • 更好的 TypeScript 推导
  • 更好的运行时性能(编译优化)
  • 更符合直觉的开发体验
开发工具改进

1. Vue DevTools 3

复制代码
Vue2 DevTools                    Vue3 DevTools
├── Components                   ├── Components
│   └── 基础信息                  │   ├── 组件树
├── Vuex                         │   ├── Props/State
│   └── 状态管理                  │   ├── Setup bindings ← 新增
├── Events                       │   └── Renders 性能追踪 ← 新增
└── Routing                      ├── Pinia ← 新的状态管理
                                 ├── Routes
                                 ├── Timeline ← 新增
                                 │   └── 事件时间线
                                 └── Performance ← 新增
                                     └── 组件性能分析

2. Volar(Vetur 的继任者)

vue 复制代码
<!-- Vetur (Vue2) -->
<!-- ❌ 模板类型检查弱 -->
<template>
  <div>{{ user.name }}</div>
  <!-- 如果 user 是 null,不会提示错误 -->
</template>

<!-- Volar (Vue3) -->
<!-- ✅ 强大的模板类型检查 -->
<template>
  <div>{{ user.name }}</div>
  <!-- ❌ 编辑器会提示:对象可能为 null -->
</template>

<script setup lang="ts">
const user = ref<User | null>(null)
// Volar 会追踪到模板中的类型问题
</script>

Volar 特性:

  • 完整的 TypeScript 支持
  • 模板类型检查
  • 更快的性能
  • 更好的代码提示
  • 重构支持(重命名、查找引用等)
生态工具对比总结
工具/特性 Vue2 Vue3
构建工具 Vue CLI (Webpack) Vite(推荐)/ Vue CLI
冷启动 30-60s 1-2s
HMR 2-5s <100ms
TypeScript vue-class-component 原生支持
IDE 插件 Vetur Volar
DevTools 基础版 增强版(性能分析等)
代码量 基准 -40%(script setup)

三、状态管理演进

面试问题3:Pinia 相比 Vuex 有哪些改进?为什么 Vue3 推荐使用 Pinia?

详细解答:

Vuex 的问题(Vue2 主要方案)

1. 繁琐的模块定义

javascript 复制代码
// Vuex 的模块定义
// store/modules/user.js
export default {
  namespaced: true,  // 必须使用命名空间
  
  state: () => ({
    userInfo: null,
    token: ''
  }),
  
  getters: {
    // ❌ 参数很繁琐
    isLoggedIn: (state, getters, rootState, rootGetters) => {
      return !!state.token
    }
  },
  
  mutations: {
    // ❌ 必须通过 mutations 修改状态
    SET_USER(state, user) {
      state.userInfo = user
    },
    SET_TOKEN(state, token) {
      state.token = token
    }
  },
  
  actions: {
    // ❌ 不能直接修改 state,必须 commit mutation
    async login({ commit }, { username, password }) {
      const { data } = await api.login({ username, password })
      commit('SET_USER', data.user)
      commit('SET_TOKEN', data.token)
    }
  }
}

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    user
  }
})

使用 Vuex:

vue 复制代码
<script>
import { mapState, mapGetters, mapActions } from 'vuex'

export default {
  computed: {
    // ❌ 需要使用 mapState/mapGetters
    ...mapState('user', ['userInfo']),
    ...mapGetters('user', ['isLoggedIn'])
  },
  
  methods: {
    // ❌ 需要使用 mapActions
    ...mapActions('user', ['login']),
    
    async handleLogin() {
      await this.login({
        username: 'admin',
        password: '123456'
      })
    }
  }
}
</script>

<!-- 或者使用完整路径 -->
<script>
export default {
  computed: {
    userInfo() {
      // ❌ 字符串路径,容易出错,没有类型提示
      return this.$store.state.user.userInfo
    },
    isLoggedIn() {
      return this.$store.getters['user/isLoggedIn']
    }
  },
  
  methods: {
    async handleLogin() {
      // ❌ dispatch 需要字符串
      await this.$store.dispatch('user/login', {
        username: 'admin',
        password: '123456'
      })
    }
  }
}
</script>

Vuex 的问题:

  1. ❌ 必须区分 mutations 和 actions(同步/异步)
  2. ❌ 命名空间字符串,容易出错
  3. ❌ TypeScript 支持差
  4. ❌ 需要大量样板代码
  5. ❌ 热更新支持不好

2. TypeScript 支持困难

typescript 复制代码
// Vuex + TypeScript 需要复杂的类型定义
import { Store } from 'vuex'

// 定义 state 类型
interface UserState {
  userInfo: User | null
  token: string
}

// 定义 RootState
interface RootState {
  user: UserState
}

// 扩展 Vue 的类型
declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    $store: Store<RootState>
  }
}

// ❌ 即使这样,类型提示还是不完整
this.$store.state.user.userInfo  // 勉强有类型
this.$store.getters['user/isLoggedIn']  // 没有类型提示
this.$store.dispatch('user/login', data)  // 没有参数类型检查
Pinia 的优势(Vue3 官方推荐)

1. 简洁直观的 API

typescript 复制代码
// stores/user.ts
import { defineStore } from 'pinia'

interface User {
  id: number
  name: string
}

// ✅ 使用组合式风格(推荐)
export const useUserStore = defineStore('user', () => {
  // state
  const userInfo = ref<User | null>(null)
  const token = ref('')
  
  // getters (computed)
  const isLoggedIn = computed(() => !!token.value)
  
  // actions (functions)
  const login = async (username: string, password: string) => {
    const { data } = await api.login({ username, password })
    userInfo.value = data.user
    token.value = data.token
  }
  
  const logout = () => {
    userInfo.value = null
    token.value = ''
  }
  
  return {
    // state
    userInfo,
    token,
    // getters
    isLoggedIn,
    // actions
    login,
    logout
  }
})

// ✅ 或使用选项式风格
export const useUserStore = defineStore('user', {
  state: () => ({
    userInfo: null as User | null,
    token: ''
  }),
  
  getters: {
    isLoggedIn: (state) => !!state.token
  },
  
  actions: {
    async login(username: string, password: string) {
      const { data } = await api.login({ username, password })
      this.userInfo = data.user
      this.token = data.token
    }
  }
})

使用 Pinia:

vue 复制代码
<script setup>
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// ✅ 直接使用,完整的类型提示
console.log(userStore.userInfo)      // User | null
console.log(userStore.isLoggedIn)    // boolean

// ✅ 直接调用 action,完整的类型检查
await userStore.login('admin', '123456')  // ✅ 参数类型检查

// ✅ 支持解构(使用 storeToRefs)
import { storeToRefs } from 'pinia'
const { userInfo, isLoggedIn } = storeToRefs(userStore)
const { login, logout } = userStore

// ✅ 直接修改 state(无需 mutation)
userStore.userInfo = newUser
userStore.token = newToken

// ✅ 批量修改
userStore.$patch({
  userInfo: newUser,
  token: newToken
})
</script>

2. 完美的 TypeScript 支持

typescript 复制代码
// ✅ 完整的类型推导
const userStore = useUserStore()

// userStore.userInfo: User | null
// userStore.token: string
// userStore.isLoggedIn: boolean
// userStore.login: (username: string, password: string) => Promise<void>

// ✅ IDE 智能提示
userStore.  // 自动显示所有可用的 state/getters/actions

// ✅ 编译时类型检查
userStore.login('admin', 123)  // ❌ 错误:参数类型不匹配
userStore.nonExistent          // ❌ 错误:属性不存在

3. 模块化自动支持

typescript 复制代码
// stores/user.ts
export const useUserStore = defineStore('user', () => {
  // ...
})

// stores/cart.ts
export const useCartStore = defineStore('cart', () => {
  const items = ref([])
  
  // ✅ 可以直接使用其他 store
  const userStore = useUserStore()
  
  const checkout = () => {
    if (!userStore.isLoggedIn) {
      throw new Error('Please login first')
    }
    // ...
  }
  
  return { items, checkout }
})

// ✅ 不需要手动注册模块,自动工作!

4. DevTools 集成

typescript 复制代码
// ✅ 完整的 DevTools 支持
// - 时间旅行调试
// - 状态编辑
// - Actions 追踪
// - 性能分析

const store = useUserStore()

// DevTools 会显示:
// user
// ├── State
// │   ├── userInfo: { ... }
// │   └── token: "..."
// ├── Getters
// │   └── isLoggedIn: true
// └── Actions
//     ├── login (timestamp, duration)
//     └── logout (timestamp, duration)

5. 插件系统

typescript 复制代码
// 持久化插件示例
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

// 使用持久化
export const useUserStore = defineStore('user', () => {
  // ...
}, {
  persist: true  // ✅ 自动持久化到 localStorage
})

// 自定义插件
function myPiniaPlugin({ store }) {
  // 为所有 store 添加属性
  store.customProperty = 'custom value'
  
  // 订阅 actions
  store.$onAction(({ name, args, after }) => {
    console.log(`Action ${name} called with:`, args)
    
    after((result) => {
      console.log(`Action ${name} completed with:`, result)
    })
  })
}

pinia.use(myPiniaPlugin)

6. 更小的包体积

复制代码
Vuex 4.x: ~7KB (gzipped)
Pinia:    ~1KB (gzipped)

减少:~86%
实际对比示例

购物车场景:

typescript 复制代码
// Vuex 实现
// store/modules/cart.js
export default {
  namespaced: true,
  state: () => ({
    items: []
  }),
  getters: {
    totalPrice: (state) => {
      return state.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
    },
    itemCount: (state) => {
      return state.items.reduce((sum, item) => sum + item.quantity, 0)
    }
  },
  mutations: {
    ADD_ITEM(state, product) {
      const item = state.items.find(i => i.id === product.id)
      if (item) {
        item.quantity++
      } else {
        state.items.push({ ...product, quantity: 1 })
      }
    },
    REMOVE_ITEM(state, productId) {
      const index = state.items.findIndex(i => i.id === productId)
      if (index > -1) {
        state.items.splice(index, 1)
      }
    }
  },
  actions: {
    addToCart({ commit }, product) {
      commit('ADD_ITEM', product)
    },
    removeFromCart({ commit }, productId) {
      commit('REMOVE_ITEM', productId)
    }
  }
}

// Pinia 实现
// stores/cart.ts
export const useCartStore = defineStore('cart', () => {
  const items = ref<CartItem[]>([])
  
  const totalPrice = computed(() => 
    items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
  )
  
  const itemCount = computed(() =>
    items.value.reduce((sum, item) => sum + item.quantity, 0)
  )
  
  const addToCart = (product: Product) => {
    const item = items.value.find(i => i.id === product.id)
    if (item) {
      item.quantity++
    } else {
      items.value.push({ ...product, quantity: 1 })
    }
  }
  
  const removeFromCart = (productId: number) => {
    const index = items.value.findIndex(i => i.id === productId)
    if (index > -1) {
      items.value.splice(index, 1)
    }
  }
  
  return {
    items,
    totalPrice,
    itemCount,
    addToCart,
    removeFromCart
  }
})

// 代码量:Pinia 减少约 40%
// 类型安全:Pinia 完全类型安全
// 可维护性:Pinia 更直观
对比总结
特性 Vuex 4 Pinia
mutations 必需 不需要
TypeScript 支持有限 完美支持
命名空间 手动配置 自动处理
代码量 少 40%
DevTools 基础支持 完整支持
热更新 部分支持 完整支持
插件系统 有限 强大灵活
包体积 7KB 1KB
Vue 3 优化 有限 完全优化

Vue 3 官方推荐: Pinia 现在是 Vue 生态的官方状态管理库。

相关推荐
徐同保2 小时前
vue.config.ts配置代理解决跨域,配置开发环境开启source-map
前端·javascript·vue.js
蒹葭玉树2 小时前
【C++上岸】C++常见面试题目--操作系统篇(第二十九期)
java·c++·面试
学历真的很重要2 小时前
【系统架构师】第一章 计算机系统基础知识(详解版)
学习·职场和发展·系统架构·系统架构师
Hexene...2 小时前
【前端Vue】npm install时根据新的状态重新引入实际用到的包,不引入未使用到的
前端·vue.js·npm
cyforkk2 小时前
14、Java 基础硬核复习:数据结构与集合源码的核心逻辑与面试考点
java·数据结构·面试
2301_780669862 小时前
Vue(入门配置、常用指令)、Ajax、Axios
前端·vue.js·ajax·javaweb
Warren982 小时前
Pytest Fixture 到底该用 return 还是 yield?
数据库·oracle·面试·职场和发展·单元测试·pytest·pyqt
Big Cole3 小时前
PHP 面试:MySQL 核心问题之索引与优化
mysql·面试·php
我是ed.3 小时前
Vue3 音频标注插件 wavesurfer
前端·vue.js·音视频