我有个技术全面、经验丰富的小团队,可承接各类网站、跨端、小程序等项目,有需要可私信我
引言:为什么现代前端应用需要状态管理
Vue中每个组件都有自己的数据、模板和方法。数据也被称之为状态,通过methods中方法改变状态来更新视图,在单个组件中修改状态更新视图是很方便的,但是实际开发中是
1)多个组件(还有多层组件嵌套)共享同一个状态
2)兄弟组建需要通信
此时传参就会很繁琐,就需要进行状态管理,负责组件间状态共享和管理,方便维护代码。接下来将深入探讨Vue状态管理的演进历程、核心原理以及在实际项目中的最佳实践。
一、Vuex
Vuex 是 Vue.js 官方的状态管理库,用于集中管理 Vue 应用中的所有组件的共享状态(数据)。
1.1 Vuex基本使用
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
// 单一数据源
state: {
count: 0,
user: null,
todos: []
},
// 计算属性
getters: {
completedTodos: state => {
return state.todos.filter(todo => todo.completed)
},
todoCount: (state, getters) => {
return getters.completedTodos.length
}
},
// 同步修改 State 中数据
mutations: {
increment(state) {
state.count++
},
setUser(state, user) {
state.user = user
},
addTodo(state, todo) {
state.todos.push(todo)
}
},
// 异步处理业务逻辑
actions: {
// 异步登录
async login({ commit }, credentials) {
const user = await api.login(credentials);
commit('setUser', user);
return user
},
// 异步获取代办列表
async fetchTodos({ commit }) {
const todos = await api.getTodos();
commit('setTodos', todos);
}
},
// Store拆分成多个模块
modules: {
// 子模块
cart: {
namespaced: true,
state: { items: [] },
mutations: {
addItem(state, item) {
state.items.push(item)
}
}
}
}
})
1.2 Vuex原理解析及核心概念
Vuex的核心思想是将组件的共享状态抽取出来,以一个全局单例模式进行统一管理,使得状态的变化可预测、可追踪。
Vuex的核心原理在于理解:它如何作为一个响应式的中央事件总线,在“状态变化”和“组件更新”之间建立可预测、可追踪的桥梁。
Vuex设计思想严格遵循了Flux 架构的单向数据流思想。Flux架构的核心是为了解决 MVC 架构中数据流混乱、难以追踪的问题,其核心流程可以概括为:
View -> Action -> Dispatcher -> Store -> View
Vuex 将其思想精炼并适配到 Vue 的响应式系统中:
1)单向数据流:数据有一个清晰的、可预测的流动方向。组件不能直接修改状态,必须通过派发 Action来发起变更。
2)集中式存储:所有共享的状态(State) 都被集中存储在一个单一的 Store 中,成为应用的“唯一数据源”。
3)状态变更可预测:状态的改变必须通过提交 Mutation这个同步过程来完成,这使得每一次状态变更都可被追踪和记录。
Vuex 的工作原理本质上是在 Vue 的响应式系统之上,构建了一套强约束的“状态变更状态机”。
4)利用 Vue 响应式:实现数据到视图的自动绑定。
Vuex 的五大核心概念
State:驱动应用的单一数据源,即存储共享状态的对象。
Getters:从 State 中派生出的计算属性,用于对 State 进行过滤、组合或计算。
Mutations:唯一能同步修改 State 中数据的同步函数,以确保状态的变更是可追踪的。
Actions:负责处理异步操作和业务逻辑,但不直接修改 State。
Modules:将单一的Store拆分成多个模块。每个模块拥有自己的State、Getters、Mutations、Actions,甚至可以嵌套子模块。
1.3 Vuex的局限性
虽然Vuex在Vue2时代是主流选择,但它也存在一些问题:
· TypeScript支持不够友好
· 代码冗长:需要定义state、mutations、actions、getters等多个部分
· 模块使用复杂:特别是带命名空间的模块
· 单一Store限制:大型项目难以维护
二、Pinia
Pinia是Vue3官方推荐的状态管理库,可以看作是"Vuex 5"。
它保留了Vuex的核心优势,同时提供了更简洁直观的API、完整的TypeScript支持,并移除了过去的一些复杂性。
Pinia的特点是"轻量、直观、类型安全",让状态管理变得更简单自然。
2.1 Pinia基本使用
简单的用户登录、退出操作。
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
// State
state: () => ({
user: null as User | null,
token: '',
permissions: [] as string[]
}),
// Getters
getters: {
isLoggedIn: (state) => !!state.token,
hasPermission: (state) => (permission: string) => {
return state.permissions.includes(permission)
}
},
// Actions (支持同步和异步)
actions: {
async login(credentials: LoginCredentials) {
const response = await api.login(credentials);
this.user = response.user;
this.token = response.token;
this.permissions = response.permissions;
// 自动保存到localStorage
localStorage.setItem('token', this.token);
},
logout() {
this.$reset(); // 重置state
localStorage.removeItem('token');
},
},
// 持久化配置 使用vuex-persistedstate插件
persist: {
enabled: true,
strategies: [{
key: 'user',
storage: localStorage,
paths: ['token', 'user.id', 'user.name']
}]
}
})
2.2 Pinia原理解析及核心概念
Pinia 的设计理念:
· 轻量直观:减少概念,降低学习成本
· 类型安全:支持TypeScript
· 组合友好:与 Vue 3 组合式 API 完美融合
2.2.1 Store :响应式状态容器
Pinia 的核心是一个响应式的状态容器,与Vuex类似。
· 底层依赖Vue响应式系统:Pinia 的 state 实际上是 reactive() 创建的响应式对象,将Store的状态转换为响应式对象
· 自动追踪依赖:组件中使用 Store 时,Vue 会自动建立响应式依赖关系
· 精确更新:当 state 变化时,只更新依赖该 state 的组件
· 变更统一:同步和异步操作使用同一套 API,actions 直接修改响应式 state
// 伪代码展示原理
function createPinia() {
const stores = new Map()
// 每个 Store 本质是一个 reactive 对象
function defineStore(id, options) {
const state = reactive(options.state())
const actions = {} // 可直接修改state
return function useStore() {
// 返回的 Store 实例是响应式的
return { $state: state, ...actions }
}
}
}
2.2.2 Getters:基于计算属性实现
Getters是基于计算属性computed来实现的。
· 自动缓存:依赖不变时不会重新计算
· 响应式依赖追踪:自动追踪 getter 函数中使用的响应式数据
· 延迟执行/懒加载:被访问时才会计算
· 链式支持: 一个 getter 可以依赖其他 getter 的结果进行计算,形成调用链
注意链式支持:避免循环依赖,Getter A 依赖 Getter B,Getter B 又依赖 Getter A
// 伪代码:getters 实现原理
function createGetters(store, getters) {
const computedGetters = {};
// 遍历getters中的各个属性
for (const [key, fn] of Object.entries(getters)) {
computedGetters[key] = computed(() => {
// 将 store 作为上下文传入
return fn.call(store)
})
}
return computedGetters
}
2.2.3 Actions:状态修改的直接性及统一的上下文
· 唯一修改入口:所有状态修改都通过 action
· 支持异步:天然支持 Promise 和 async/await
· 完整访问权限:action中的this自动指向 store 实例,可以访问 this.state、getters、其他action等
// 伪代码:actions 绑定
function bindActions(store, actions) {
for (const [key, action] of Object.entries(actions)) {
store[key] = function(...args) {
// 确保 action 中的 this 指向 store 实例
return action.call(store, ...args)
}
}
}
2.2.4 模块化设计原理
Pinia 采用 扁平化的模块系统。
· 每个 Store 独立,无需嵌套;
· 按需导入:可访问已导入store中的action
· 自动代码分割:配合构建工具实现按需加载
// 伪代码:Store 注册原理
class Pinia {
constructor() {
this._stores = new Map() // 存储所有注册的 Store
}
// 每个 Store 独立注册
registerStore(id, store) {
// 不进行嵌套组织,保持扁平
this._stores.set(id, store)
}
}
// 使用:通过导入实现 Store 间通信
import { useUserStore } from './user'
import { useCartStore } from './cart'
const userStore = useUserStore()
const cartStore = useCartStore()
// 在一个 action 中访问另一个 Store
cartStore.actions.checkout() {
if (!userStore.isLoggedIn) {
// 一些操作
}
}
2.2.5 插件系统原理
Pinia 的插件系统基于中间件模式,本质是在 store 的各个生命周期关键点插入钩子函数,允许插件拦截和增强 store 的行为。
// 插件结构
const myPlugin = (context) => {
// 1. 初始化阶段 - store创建时执行一次
console.log('插件安装', context.store.$id)
// 2. 拦截action调用
context.store.$onAction(({ name, store, args, after, onError }) => {
console.log(`Action ${name} 开始执行`, args);
after((result) => {
console.log(`Action ${name} 执行完成`, result);
})
onError((error) => {
console.error(`Action ${name} 执行失败`, error);
})
})
// 3. 监听state变化
context.store.$subscribe((mutation, state) => {
console.log('state变化:', mutation.type, mutation.payload);
})
// 4. 可以添加新的属性或方法
return {
// 返回的对象会合并到store中
customMethod() {
console.log('自定义插件方法');
}
}
}
常见插件类型
· 状态持久化:将 state 保存到 localStorage
· 日志记录:记录所有 action 调用和 state 变化
· 错误监控:统一捕获 action 执行错误
· 权限控制:拦截特定 action,检查用户权限
· 数据同步:在 state 变化时同步到服务器
· 性能监控:测量 action 执行时间
2.3 Pinia总结
简单来说Pinia 的核心原理就是:用最少的抽象,将 Vue 的响应式能力组织成可维护、可扩展、类型安全的状态管理方案。
其精妙之处在于它的简约而不简单:
1)利用现有能力:深度集成 Vue 3 的响应式系统,不重复造轮子
2)移除抽象层:去掉 mutation 概念,让状态修改更直观
3)拥抱 TypeScript:从设计之初就考虑类型安全
4)保持灵活性:插件系统提供强大的扩展能力
5)开发者体验优先:API 设计以直观易用为目标
三、状态管理在实际项目中应用
3.1 自定义持久化插件
自定义持久化插件,解决页面刷新后状态丢失问题。如保存用户偏好设置等。
// 自定义插件
const localStoragePlugin = (context: PiniaPluginContext) => {
// 从localStorage恢复
const stored = localStorage.getItem(context.store.$id)
// 白名单模式:只对指定的 Store 启用持久化
const whitelist = ['user-preferences', 'theme-settings'] // 只在这些 Store 中生效
if (!whitelist.includes(storeId)) {
return // 不在白名单中,直接返回,不应用持久化
}
// 另一种模式:黑名单模式:名单内的不需要持久化
const blacklist = ['session-data', 'temporary-cache'] // 这些 Store 不持久化
if (blacklist.includes(storeId)) {
return
}
// 另一种模式:精准匹配,使用正则
const pattern = /.*-preferences$/ // 匹配以 "-preferences" 结尾的 Store
if (!pattern.test(storeId)) {
return
}
// 持久化操作逻辑
if (stored) {
context.store.$patch(JSON.parse(stored));
}
// 监听变化并保存
context.store.$subscribe((mutation, state) => {
localStorage.setItem(context.store.$id, JSON.stringify(state))
})
}
// 使用插件
const pinia = createPinia();
pinia.use(localStoragePlugin();
// 或者使用已有插件pinia-plugin-persistedstate
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
3.2 模块化与组合Store
使用Pinia扁平化的模块化思想,各store作为对立模块导入,组合式模块可扩展,便于复用基础模块。如权限验证,免登录跳转等。
export const useExtendedAuthStore = defineStore('extendedAuth', () => {
// 组合式API:实现模块间的灵活组合
const authStore = useAuthStore() // 引入基础模块
// 扩展新功能
const loginAttempts = ref(0)
const loginWithRetry = async () => {
// 复用基础模块的功能
await authStore.login();
// 添加新逻辑
loginAttempts.value = 0
}
return {
...authStore, // 继承基础模块
loginAttempts, // 新增状态
loginWithRetry // 新增行为
}
})
四、大型项目状态管理架构设计
4.1 分层架构设计
src/
├── stores/
│ ├── index.ts # Store注册入口
│ ├── modules/ # 业务模块
│ │ ├── auth/
│ │ │ ├── index.ts # auth store
│ │ │ ├── types.ts # 类型定义
│ │ │ └── api.ts # API调用
│ │ ├── user/
│ │ └── product/
│ ├── shared/ # 共享store
│ │ ├── ui.ts # UI状态
│ │ └── notification.ts # 通知状态
│ └── plugins/ # 插件
│ ├── persistence.ts
│ └── logger.ts
4.2 基于Domain的状态管理
Domain-Driven 状态管理是强调按业务领域(Domain)组织状态,而非按传统技术分层次(如 UI、业务、数据等)。
Domain 分层,如用户认证域:UserAuthStore、 商品目录域:ProductCatalogStore 、购物车域:ShoppingCartStore、订单域:OrderProcessingStore等。
// 领域模型驱动设计
export class ProductDomain {
constructor(private store: ReturnType<typeof useProductStore>) {}
// 业务方法-添加到购物车
async addToCart(productId: number, quantity: number) {
const cartStore = useCartStore()
// 验证库存
const product = this.store.products.find(p => p.id === productId);
if (!product || product.stock < quantity) {
throw new Error('库存不足')
}
// 更新库存
this.store.updateStock(productId, product.stock - quantity);
// 添加到购物车
cartStore.addItem({
productId,
quantity,
price: product.price
})
}
// 批量操作
async importProducts(products: ImportProduct[]) {
// 验证数据
const validProducts = this.validateImportProducts(products);
// 批量更新
await this.store.batchUpdateProducts(validProducts);
// 发送通知
notificationStore.success(`成功导入${validProducts.length}个商品`);
}
}
// 在组件中使用
const productDomain = new ProductDomain(useProductStore());
productDomain.addToCart(1, 2).catch(error => {
// 统一错误处理
notificationStore.error(error.message)
})
五、高频面试题解析
问题1: Vuex和Pinia的主要区别是什么,如何考虑用那种
主要区别:
API设计:Vuex需要state/mutations/actions/getters,Pinia更简洁:state/actions/getters
TypeScript支持:Pinia提供完整的类型推断
模块系统:Pinia扁平化设计,天然支持多个store,无需命名空间
体积:Pinia更轻量,约1KB
Composition API:Pinia完全兼容Composition API
设计考虑:
新项目优先Pinia:特别是Vue3项目
维护老项目用Vuex:避免重构成本
需要插件生态用Vuex:Vuex有更丰富的插件
考虑团队熟悉度:团队熟悉哪个用哪个
问题2: 如何在大型项目中组织Store
参考答案:
按业务领域划分:auth、user、product等
共享基础Store:ui、notification等
使用组合Store:依赖于其扁平化设计,复用逻辑
统一类型定义:共享TypeScript接口
插件系统:统一处理持久化、日志等
// 示例:模块化组织
stores/
├── auth/ # 认证相关
├── user/ # 用户管理
├── product/ # 商品管理
├── order/ # 订单管理
├── shared/ # 共享状态
│ ├── ui.ts
│ └── loading.ts
└── plugins/ # 全局插件
问题3: 如何实现Store的持久化
参考答案:
使用插件:pinia-plugin-persistedstate
自定义插件:监听变化自动保存
选择性持久化:只持久化重要数据
加密敏感数据:如token
// 自定义持久化
const persistencePlugin = ({ store }) => {
// 恢复
const saved = localStorage.getItem(store.$id);
if (saved) {
store.$patch(JSON.parse(saved));
}
// 保存
store.$subscribe((mutation, state) => {
localStorage.setItem(store.$id, JSON.stringify(state));
})
}
// 使用pinia-plugin-persistedstate
defineStore('user', {
persist: {
key: 'user-store',
storage: localStorage,
paths: ['token', 'user.id', 'user.name'],
serializer: {
serialize: JSON.stringify,
deserialize: JSON.parse
}
}
})
六、总结
Vue状态管理演进趋势
从集中式到分布式:单一Store → 多个Store
从复杂到简洁:减少样板代码
更好的TypeScript支持:完整的类型安全
组合式API整合:更好的逻辑复用
项目实践建议
✅ 推荐做法:
新项目优先使用Pinia
按业务领域组织Store
合理使用TypeScript类型定义
考虑持久化策略
❌ 避免做法:
不要过度使用全局状态
避免Store过于臃肿
不要忘记清理订阅和副作用
避免直接修改其他Store的状态
性能优化要点
避免大对象响应式:使用shallowRef/shallowReactive
合理使用计算属性:避免重复计算
按需加载Store:动态导入大型Store
批量更新:使用$patch减少更新次数
下期预告
下一篇我们将深入探讨 Vue Router的进阶用法,包括路由守卫、懒加载、动态路由、路由元信息等高级特性,以及如何实现权限路由和路由过渡动画。路由是单页应用的核心,掌握它将让你的应用更加专业!
如果觉得有帮助,请关注+点赞+收藏,这是对我最大的鼓励! 如有问题,请评论区留言

被折叠的 条评论
为什么被折叠?



