模块化状态管理
随着 Vue 3 项目规模的增长,单一的 Store 文件可能变得臃肿,难以维护。Pinia 的模块化设计允许将状态管理拆分为多个独立的 Store,每个 Store 专注于特定功能模块。本节将讲解如何使用 Pinia 实现模块化状态管理,探讨模块间的通信方式,并通过示例展示其在实际项目中的应用,帮助你构建清晰、可扩展的状态管理结构。
为什么要模块化?
在大型应用中,状态可能涉及用户、产品、订单等多个领域。如果所有状态集中在一个 Store 中,会导致:
- 代码混乱:逻辑分散,难以定位。
- 命名冲突:多个功能共享命名空间。
- 维护困难:修改一处可能影响全局。
模块化状态管理的优点:
- 职责分离:每个模块独立管理自己的状态。
- 可复用性:模块可在不同项目间复用。
- 清晰性:按功能组织,便于团队协作。
Pinia 的模块化实现
Pinia 通过 defineStore 创建独立的 Store,每个 Store 是一个模块,无需显式的模块配置。
基本模块化
用户模块
// src/stores/user.js
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useUserStore = defineStore('user', () => {
const user = ref({ name: '', isLoggedIn: false });
const login = (name) => {
user.value = { name, isLoggedIn: true };
};
const logout = () => {
user.value = { name: '', isLoggedIn: false };
};
return { user, login, logout };
});
产品模块
// src/stores/product.js
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useProductStore = defineStore('product', () => {
const products = ref([
{ id: 1, name: '苹果', price: 2 },
{ id: 2, name: '香蕉', price: 1 }
]);
const addProduct = (product) => {
products.value.push({ id: Date.now(), ...product });
};
return { products, addProduct };
});
使用
<template>
<div>
<p v-if="user.isLoggedIn">欢迎,{{ user.name }}</p>
<button v-if="!user.isLoggedIn" @click="login('Alice')">登录</button>
<button v-else @click="logout">退出</button>
<ul>
<li v-for="product in products" :key="product.id">
{{ product.name }} - ${{ product.price }}
</li>
</ul>
<button @click="addProduct({ name: '橙子', price: 3 })">添加产品</button>
</div>
</template>
<script>
import { useUserStore } from '@/stores/user';
import { useProductStore } from '@/stores/product';
export default {
setup() {
const userStore = useUserStore();
const productStore = useProductStore();
return {
user: userStore.user,
login: userStore.login,
logout: userStore.logout,
products: productStore.products,
addProduct: productStore.addProduct
};
}
};
</script>
- 效果:登录/退出管理用户状态,添加产品更新列表。
模块间通信
Pinia 的 Store 是独立的,但可以通过导入其他 Store 实现通信。
示例:购物车模块与产品模块联动
// src/stores/cart.js
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { useProductStore } from './product';
export const useCartStore = defineStore('cart', () => {
const cartItems = ref([]);
const productStore = useProductStore();
const addToCart = (productId) => {
const product = productStore.products.find(p => p.id === productId);
if (product) {
cartItems.value.push({ ...product, quantity: 1 });
}
};
const total = () => {
return cartItems.value.reduce((sum, item) => sum + item.price * item.quantity, 0);
};
return { cartItems, addToCart, total };
});
使用
<template>
<div>
<h2>产品列表</h2>
<ul>
<li v-for="product in products" :key="product.id">
{{ product.name }} - ${{ product.price }}
<button @click="addToCart(product.id)">加入购物车</button>
</li>
</ul>
<h2>购物车</h2>
<ul>
<li v-for="item in cartItems" :key="item.id">
{{ item.name }} - ${{ item.price }} x {{ item.quantity }}
</li>
</ul>
<p>总价:${{ total }}</p>
</div>
</template>
<script>
import { useProductStore } from '@/stores/product';
import { useCartStore } from '@/stores/cart';
export default {
setup() {
const productStore = useProductStore();
const cartStore = useCartStore();
return {
products: productStore.products,
addToCart: cartStore.addToCart,
cartItems: cartStore.cartItems,
total: cartStore.total()
};
}
};
</script>
- 效果:点击“加入购物车”将产品添加到购物车,动态计算总价。
模块化最佳实践
1. 文件结构
src/
├── stores/
│ ├── user.js
│ ├── product.js
│ ├── cart.js
│ └── index.js # 可选:统一导出
├── components/
└── main.js
统一导出(可选)
// src/stores/index.js
export * from './user';
export * from './product';
export * from './cart';
2. 命名规范
- Store ID:使用功能名称(如
'user'、'cart')。 - 函数名:以
useXxxStore命名,便于识别。
3. 状态隔离与共享
- 隔离:每个 Store 管理独立状态。
- 共享:通过导入其他 Store 访问数据,避免全局状态污染。
4. 动态模块
Pinia 支持动态创建 Store:
import { defineStore } from 'pinia';
const useDynamicStore = (id) => defineStore(`dynamic-${id}`, () => {
const data = ref(0);
return { data };
});
export default useDynamicStore;
使用
<script>
import useDynamicStore from '@/stores/dynamic';
export default {
setup() {
const store1 = useDynamicStore('one')();
const store2 = useDynamicStore('two')();
return { data1: store1.data, data2: store2.data };
}
};
</script>
- 效果:创建多个独立实例。
注意事项
- 避免循环依赖:
- Store A 导入 B,B 再导入 A 可能导致问题,尽量单向依赖。
- 性能优化:
- 避免在 Store 中存储过多数据,使用按需加载。
- 调试:
- 使用 Vue DevTools 检查模块状态。
综合示例
模块化任务管理
// src/stores/auth.js
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useAuthStore = defineStore('auth', () => {
const user = ref(null);
const login = (name) => { user.value = { name }; };
return { user, login };
});
// src/stores/tasks.js
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { useAuthStore } from './auth';
export const useTaskStore = defineStore('tasks', () => {
const tasks = ref([]);
const authStore = useAuthStore();
const addTask = (text) => {
if (authStore.user) {
tasks.value.push({ id: Date.now(), text, user: authStore.user.name });
}
};
return { tasks, addTask };
});
使用
<template>
<div>
<button v-if="!user" @click="login('Alice')">登录</button>
<div v-else>
<p>用户:{{ user.name }}</p>
<input v-model="newTask" @keyup.enter="addTask" />
<ul>
<li v-for="task in tasks" :key="task.id">{{ task.text }} - {{ task.user }}</li>
</ul>
</div>
</div>
</template>
<script>
import { useAuthStore } from '@/stores/auth';
import { useTaskStore } from '@/stores/tasks';
import { ref } from 'vue';
export default {
setup() {
const authStore = useAuthStore();
const taskStore = useTaskStore();
const newTask = ref('');
const addTask = () => {
taskStore.addTask(newTask.value);
newTask.value = '';
};
return {
user: authStore.user,
login: authStore.login,
tasks: taskStore.tasks,
newTask,
addTask
};
}
};
</script>
- 效果:登录后添加任务,记录用户归属。
总结
Pinia 的模块化状态管理通过独立 Store 实现职责分离,借助导入机制实现模块通信。合理的文件结构和命名规范能进一步提升代码质量。通过本节的实践,你已掌握如何在 Vue 3 项目中组织模块化状态。下一节将通过购物车案例巩固你的技能,探索更复杂的应用场景!
