一、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
}
})
问题分析:
-
this 上下文复杂
- data、computed、methods 都挂载在 this 上
- TypeScript 难以追踪这些属性的类型
-
运行时合并
- mixins 的类型合并困难
- r e f s 、 refs、 refs、attrs 等特殊属性类型推导不准确
-
装饰器方案的问题
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 的问题:
- ❌ 必须区分 mutations 和 actions(同步/异步)
- ❌ 命名空间字符串,容易出错
- ❌ TypeScript 支持差
- ❌ 需要大量样板代码
- ❌ 热更新支持不好
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 生态的官方状态管理库。