Vue 3 状态管理与跨组件通信完全指南
一、核心概念与方案对比
1. 可用的状态管理方案
- Composition API + Provide/Inject
- Pinia
- 自定义响应式存储
- EventBus
- Vuex (不推荐在 Vue 3 中使用)
2. 各方案适用场景
| 方案 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| Composition API | 中小型应用,组件树通信 | 轻量,灵活,TypeScript支持好 | 状态分散,不适合大型应用 |
| Pinia | 中大型应用,全局状态管理 | 类型安全,模块化,开发体验好 | 学习成本,项目体积增加 |
| 自定义存储 | 特定场景,轻量级需求 | 完全可控,体积小 | 需要自行实现功能 |
| EventBus | 跨组件事件通信 | 简单直接 | 难以追踪,耦合度高 |
二、基于 Composition API 的状态管理
1. 创建响应式状态
// stores/user.ts
import { ref, computed } from 'vue'
export function useUserStore() {
// 状态
const user = ref({
id: '',
name: '',
token: ''
})
// Getters
const isLoggedIn = computed(() => !!user.value.token)
// Actions
const login = async (credentials: { username: string; password: string }) => {
// 模拟登录请求
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
})
const data = await response.json()
user.value = {
id: data.id,
name: data.name,
token: data.token
}
}
const logout = () => {
user.value = {
id: '',
name: '',
token: ''
}
}
return {
user,
isLoggedIn,
login,
logout
}
}
2. 全局状态注入
// stores/index.ts
import { provide, inject } from 'vue'
import { useUserStore } from './user'
const StoreSymbol = Symbol('store')
export function provideStore() {
const store = {
user: useUserStore()
// 可以添加其他store
}
provide(StoreSymbol, store)
return store
}
export function useStore() {
const store = inject(StoreSymbol)
if (!store) throw new Error('Store not provided!')
return store
}
三、Pinia 状态管理
1. 定义 Store
// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
// 状态
state: () => ({
count: 0,
history: [] as number[]
}),
// Getters
getters: {
doubleCount: (state) => state.count * 2,
lastChange: (state) => state.history[state.history.length - 1]
},
// Actions
actions: {
increment() {
this.count++
this.history.push(this.count)
},
async fetchInitialCount() {
const response = await fetch('/api/count')
const data = await response.json()
this.count = data.count
}
}
})
2. Store 组合
// stores/composite.ts
import { defineStore } from 'pinia'
import { useCounterStore } from './counter'
import { useUserStore } from './user'
export const useCompositeStore = defineStore('composite', {
state: () => ({
loading: false
}),
actions: {
async initializeApp() {
this.loading = true
const counter = useCounterStore()
const user = useUserStore()
try {
await Promise.all([
counter.fetchInitialCount(),
user.checkAuth()
])
} finally {
this.loading = false
}
}
}
})
四、自定义响应式存储
1. 创建响应式存储类
// utils/reactive-store.ts
import { reactive, readonly } from 'vue'
export class ReactiveStore<T extends object> {
private state: T
constructor(initialState: T) {
this.state = reactive(initialState) as T
}
getState() {
return readonly(this.state)
}
patch(partial: Partial<T>) {
Object.assign(this.state, partial)
}
set(key: keyof T, value: T[keyof T]) {
this.state[key] = value
}
reset(initialState: T) {
Object.assign(this.state, initialState)
}
}
2. 使用示例
// stores/theme.ts
interface ThemeState {
mode: 'light' | 'dark'
primary: string
secondary: string
}
export const themeStore = new ReactiveStore<ThemeState>({
mode: 'light',
primary: '#1890ff',
secondary: '#f5f5f5'
})
// 在组件中使用
import { themeStore } from '@/stores/theme'
const theme = themeStore.getState()
const toggleTheme = () => {
themeStore.set('mode', theme.mode === 'light' ? 'dark' : 'light')
}
五、事件通信系统
1. 实现类型安全的事件总线
// utils/event-bus.ts
type EventCallback<T = any> = (payload: T) => void
class TypedEventBus {
private events = new Map<string, Set<EventCallback>>()
on<T>(event: string, callback: EventCallback<T>) {
if (!this.events.has(event)) {
this.events.set(event, new Set())
}
this.events.get(event)!.add(callback)
return () => this.off(event, callback)
}
off(event: string, callback: EventCallback) {
this.events.get(event)?.delete(callback)
}
emit<T>(event: string, payload: T) {
this.events.get(event)?.forEach(callback => callback(payload))
}
clear() {
this.events.clear()
}
}
export const eventBus = new TypedEventBus()
2. 组件中使用
// 在发送组件中
import { eventBus } from '@/utils/event-bus'
const sendMessage = () => {
eventBus.emit('chat:message', {
id: Date.now(),
text: 'Hello!',
user: 'John'
})
}
// 在接收组件中
import { onMounted, onUnmounted } from 'vue'
import { eventBus } from '@/utils/event-bus'
const unsubscribe = eventBus.on('chat:message', (message) => {
console.log('Received:', message)
})
onUnmounted(() => {
unsubscribe()
})
六、状态持久化
1. 创建持久化包装器
// utils/persistent-store.ts
interface Storage {
getItem(key: string): string | null
setItem(key: string, value: string): void
}
export function createPersistentStore<T extends object>(
key: string,
initialState: T,
storage: Storage = localStorage
) {
// 读取持久化数据
const savedState = storage.getItem(key)
const state = reactive(
savedState ? JSON.parse(savedState) : initialState
)
// 监听变化并持久化
watch(
() => state,
(newState) => {
storage.setItem(key, JSON.stringify(newState))
},
{ deep: true }
)
return readonly(state)
}
// 使用示例
const settings = createPersistentStore('app-settings', {
theme: 'light',
fontSize: 14,
notifications: true
})
七、性能优化建议
-
状态分割
- 按功能模块拆分状态
- 避免大型单一状态树
- 使用细粒度的响应式
-
computed 缓存
- 合理使用计算属性
- 避免在 getter 中进行复杂计算
-
异步操作处理
- 使用 action 统一处理异步
- 实现请求去重和缓存
- 考虑乐观更新
-
订阅优化
- 使用
shallowRef减少深层响应 - 及时取消订阅
- 避免不必要的监听
- 使用
八、最佳实践建议
-
状态设计原则
- 单一数据源
- 状态最小化
- 避免冗余数据
-
类型安全
- 使用 TypeScript
- 定义清晰的接口
- 利用类型推导
-
错误处理
- 统一的错误处理策略
- 状态回滚机制
- 错误状态管理
-
测试友好
- 状态可序列化
- 操作可追踪
- 依赖注入设计
总结
本指南涵盖了 Vue 3 中主要的状态管理方案:
- Composition API 的响应式状态
- Pinia 的标准化状态管理
- 自定义响应式存储
- 事件通信系统
- 状态持久化方案
选择合适的状态管理方案应考虑:
- 项目规模和复杂度
- 团队熟悉度
- 维护成本
- 性能需求
- 开发效率