vue 状态管理
浏览量:加载中...
状态管理工具 Vuex Pinia

vuex
1、基本配置和创建 Store
1// store/index.js 2import { createStore } from 'vuex'; 3 4const store = createStore({ 5 // 状态定义 6 state() { 7 return { 8 count: 0, 9 user: null, 10 products: [], 11 }; 12 }, 13 14 // 🍎计算属性(类似于Vue组件的computed) 15 getters: { 16 // 获取当前计数的平方 17 doubleCount: (state) => state.count * 2, 18 19 // 判断用户是否已登录 20 isLoggedIn: (state) => !!state.user, 21 22 // 获取已上架商品 23 availableProducts: (state) => { 24 return state.products.filter((product) => product.inStock); 25 }, 26 }, 27 28 // 🍌修改状态的方法(同步操作) 29 mutations: { 30 // 增加计数 31 increment(state) { 32 state.count++; 33 }, 34 35 // 设置用户信息 36 setUser(state, user) { 37 state.user = user; 38 }, 39 40 // 设置产品列表 41 setProducts(state, products) { 42 state.products = products; 43 }, 44 }, 45 46 // 🌰 处理异步操作 47 actions: { 48 // 异步增加计数 49 async incrementAsync({ commit }) { 50 // 模拟API调用延迟 51 await new Promise((resolve) => setTimeout(resolve, 1000)); 52 commit('increment'); 53 }, 54 55 // 异步获取用户信息 56 async fetchUser({ commit }, userId) { 57 try { 58 // 实际项目中这里会调用API 59 // const response = await api.getUser(userId) 60 // const userData = response.data 61 62 // 模拟用户数据 63 const userData = { 64 id: userId, 65 name: '张三', 66 email: 'zhangsan@example.com', 67 }; 68 69 commit('setUser', userData); 70 } catch (error) { 71 console.error('获取用户信息失败:', error); 72 } 73 }, 74 }, 75}); 76 77export default store;
2. 在 Vue 组件中使用 Vuex
1<template> 2 <div class="vuex-example"> 3 <h1>Vuex 示例</h1> 4 5 <!-- 状态展示 --> 6 <p>当前计数: {{ count }}</p> 7 <p>计数的平方: {{ doubleCount }}</p> 8 <p v-if="isLoggedIn">欢迎您, {{ user?.name }}</p> 9 10 <!-- 状态修改按钮 --> 11 <button @click="increment">增加计数</button> 12 <button @click="incrementAsync">异步增加计数</button> 13 <button @click="fetchUserData">获取用户信息</button> 14 15 <!-- 产品列表 --> 16 <div class="product-list"> 17 <h3>产品列表</h3> 18 <ul> 19 <li v-for="product in availableProducts" :key="product.id"> 20 {{ product.name }} - ¥{{ product.price }} 21 </li> 22 </ul> 23 </div> 24 </div> 25</template> 26 27<script> 28import { computed } from 'vue'; 29import { useStore } from 'vuex'; 30 31export default { 32 setup() { 33 // 🍎 1 获取store实例 34 const store = useStore(); 35 36 // 🍎 2 使用computed访问 state 和 getters 37 const count = computed(() => store.state.count); 38 const doubleCount = computed(() => store.getters.doubleCount); 39 const user = computed(() => store.state.user); 40 const isLoggedIn = computed(() => store.getters.isLoggedIn); 41 const availableProducts = computed(() => store.getters.availableProducts); 42 43 // 🍎 调用 mutations 修改状态 44 const increment = () => { 45 store.commit('increment'); 46 }; 47 48 // 🍎 调用 actions 处理异步逻辑 直接调用store中方法 49 const incrementAsync = () => { 50 store.dispatch('incrementAsync'); 51 }; 52 53 const fetchUserData = () => { 54 store.dispatch('fetchUser', 1); 55 }; 56 57 return { 58 count, 59 doubleCount, 60 user, 61 isLoggedIn, 62 availableProducts, 63 increment, 64 incrementAsync, 65 fetchUserData, 66 }; 67 }, 68}; 69</script>
3. Vuex 模块化管理
1// store/modules/user.js - 用户模块 2const userModule = { 3 // 命名空间启用 4 namespaced: true, 5 6 state() { 7 return { 8 currentUser: null, 9 permissions: [], 10 }; 11 }, 12 13 getters: { 14 isAdmin: (state) => { 15 return state.permissions.includes('admin'); 16 }, 17 }, 18 19 mutations: { 20 setCurrentUser(state, user) { 21 state.currentUser = user; 22 }, 23 setPermissions(state, permissions) { 24 state.permissions = permissions; 25 }, 26 }, 27 28 actions: { 29 async login({ commit }, credentials) { 30 try { 31 // 模拟登录API调用 32 // const response = await auth.login(credentials) 33 const userData = { 34 id: 1, 35 name: '管理员', 36 permissions: ['admin', 'user'], 37 }; 38 39 commit('setCurrentUser', userData); 40 commit('setPermissions', userData.permissions); 41 return userData; 42 } catch (error) { 43 console.error('登录失败:', error); 44 throw error; 45 } 46 }, 47 }, 48}; 49 50export default userModule;
在主入口文件引入模块文件
1// store/index.js - 主store文件 2import { createStore } from 'vuex'; 3import userModule from './modules/user'; //🍎 4import productModule from './modules/product'; 5 6const store = createStore({ 7 // 全局状态 8 state() { 9 return { 10 appName: 'My Vue App', 11 }; 12 }, 13 14 // 注册模块 15 modules: { 16 // 🍎 注册用户模块 17 user: userModule, 18 // 🍎 注册产品模块 19 product: productModule, 20 }, 21}); 22 23export default store;
4. 在组件中使用模块化的 Vuex
1<template> 2 <div> 3 <h1>{{ appName }}</h1> 4 5 <!-- 访问模块化状态 --> 6 <div v-if="currentUser"> 7 <h3>用户信息</h3> 8 <p>用户名: {{ currentUser.name }}</p> 9 <p v-if="isAdmin">您是管理员</p> 10 </div> 11 12 <div> 13 <h3>产品信息</h3> 14 <p v-if="loading">加载中...</p> 15 <p v-else>共有 {{ productCount }} 个产品</p> 16 <button @click="fetchProducts">获取产品</button> 17 </div> 18 </div> 19</template> 20 21<script> 22import { computed } from 'vue'; 23import { useStore } from 'vuex'; 24 25export default { 26 setup() { 27 const store = useStore(); 28 29 // 访问全局状态 30 const appName = computed(() => store.state.appName); 31 32 // 访问模块状态(使用命名空间) 33 const currentUser = computed(() => store.state.user.currentUser); 34 // 🍎 访问模块的 getters(使用命名空间) 35 const isAdmin = computed(() => store.getters['user/isAdmin']); 36 const loading = computed(() => store.state.product.loading); 37 const productCount = computed(() => store.getters['product/productCount']); 38 39 // 🍎 调用模块的actions 40 const fetchProducts = () => { 41 store.dispatch('product/fetchProducts'); 42 }; 43 44 return { 45 appName, 46 currentUser, 47 isAdmin, 48 loading, 49 productCount, 50 fetchProducts, 51 }; 52 }, 53}; 54</script>
pinia
. 基本配置和创建 Store
1// stores/index.js 2import { defineStore } from 'pinia'; 3 4// 创建计数器 store 5export const useCounterStore = defineStore('counter', { 6 // 状态定义(相当于Vuex的state) 7 state: () => ({ 8 count: 0, 9 name: '计数器', 10 }), 11 12 // 🍎 计算属性(相当于Vuex的getters) 13 getters: { 14 doubleCount: (state) => state.count * 2, 15 getCountWithName: (state) => { 16 return `${state.name}: ${state.count}`; 17 }, 18 }, 19 20 // 🍎 方法(同时支持同步和异步,相当于Vuex的mutations和actions的结合) 21 actions: { 22 increment() { 23 this.count++; 24 }, 25 26 // 可以直接在actions中执行异步操作 27 async incrementAsync() { 28 // 模拟异步操作 29 await new Promise((resolve) => setTimeout(resolve, 1000)); 30 this.count++; 31 }, 32 33 incrementBy(value) { 34 this.count += value; 35 }, 36 }, 37});
2. 在 Vue 组件中使用 Pinia
1<template> 2 <div class="pinia-example"> 3 <h1>Pinia 示例</h1> 4 5 <!-- 状态展示 --> 6 <p>当前计数: {{ counterStore.count }}</p> 7 <p>计数的平方: {{ counterStore.doubleCount }}</p> 8 <p>{{ counterStore.getCountWithName }}</p> 9 10 <!-- 直接修改状态 --> 11 <button @click="counterStore.count++">直接增加</button> 12 13 <!-- 通过actions修改状态 --> 14 <button @click="counterStore.increment()">增加</button> 15 <button @click="counterStore.incrementAsync()">异步增加</button> 16 <button @click="counterStore.incrementBy(10)">增加10</button> 17 18 <!-- 重置状态 --> 19 <button @click="resetStore">重置store</button> 20 </div> 21</template> 22 23<script> 24import { useCounterStore } from '@/stores/counter'; 25import { storeToRefs } from 'pinia'; 26 27export default { 28 setup() { 29 // 🍎 创建store实例 30 const counterStore = useCounterStore(); 31 32 // 使用storeToRefs确保解构的状态保持响应性 33 const { count, doubleCount } = storeToRefs(counterStore); 34 35 // 🍎 重置store状态 api 36 const resetStore = () => { 37 counterStore.$reset(); 38 }; 39 40 return { 41 counterStore, // 可以直接使用store实例 42 count, 43 doubleCount, 44 resetStore, 45 }; 46 }, 47}; 48</script>
3. Pinia 用户 Store 示例
使用组合式 API 风格定义 store
1// stores/user.js 2import { defineStore } from 'pinia'; 3import { ref, computed } from 'vue'; 4 5// 使用组合式API风格定义store 6export const useUserStore = defineStore('user', () => { 7 // 状态 - 使用ref定义 8 const user = ref(null); 9 const token = ref(''); 10 11 // 计算属性 - 使用computed定义 12 const isLoggedIn = computed(() => !!user.value); 13 const userName = computed(() => user.value?.name || '游客'); 14 15 // 方法 16 function setUser(userData) { 17 user.value = userData; 18 } 19 20 function setToken(newToken) { 21 token.value = newToken; 22 // 可以直接在这里进行本地存储 23 localStorage.setItem('token', newToken); 24 } 25 26 function logout() { 27 user.value = null; 28 token.value = ''; 29 localStorage.removeItem('token'); 30 } 31 32 async function login(credentials) { 33 try { 34 // 模拟登录API调用 35 // const response = await api.login(credentials) 36 // const data = response.data 37 38 const data = { 39 user: { 40 id: 1, 41 name: '张三', 42 email: 'zhangsan@example.com', 43 }, 44 token: 'mock-jwt-token', 45 }; 46 47 setUser(data.user); 48 setToken(data.token); 49 return data.user; 50 } catch (error) { 51 console.error('登录失败:', error); 52 throw error; 53 } 54 } 55 56 // 返回需要暴露的状态和方法 57 return { 58 user, 59 token, 60 isLoggedIn, 61 userName, 62 setUser, 63 setToken, 64 logout, 65 login, 66 }; 67});
4. Pinia 持久化存储示例
一般方式
1// stores/cart.js 2import { defineStore } from 'pinia'; 3 4export const useCartStore = defineStore('cart', { 5 state: () => ({ 6 items: [], 7 totalAmount: 0, 8 }), 9 10 getters: { 11 itemCount: (state) => state.items.length, 12 13 cartItemsWithTotal: (state) => { 14 return state.items.map((item) => ({ 15 ...item, 16 itemTotal: item.price * item.quantity, 17 })); 18 }, 19 }, 20 21 actions: { 22 addItem(product, quantity = 1) { 23 const existingItem = this.items.find((item) => item.id === product.id); 24 25 if (existingItem) { 26 // 如果商品已存在,增加数量 27 existingItem.quantity += quantity; 28 } else { 29 // 否则添加新商品 30 this.items.push({ 31 ...product, 32 quantity, 33 }); 34 } 35 36 this.updateTotal(); 37 this.saveToLocalStorage(); 38 }, 39 40 removeItem(productId) { 41 this.items = this.items.filter((item) => item.id !== productId); 42 this.updateTotal(); 43 this.saveToLocalStorage(); 44 }, 45 46 updateQuantity(productId, quantity) { 47 const item = this.items.find((item) => item.id === productId); 48 if (item) { 49 item.quantity = quantity; 50 this.updateTotal(); 51 this.saveToLocalStorage(); 52 } 53 }, 54 55 clearCart() { 56 this.items = []; 57 this.totalAmount = 0; 58 this.saveToLocalStorage(); 59 }, 60 61 updateTotal() { 62 this.totalAmount = this.items.reduce((total, item) => { 63 return total + item.price * item.quantity; 64 }, 0); 65 }, 66 67 // 保存到本地存储 68 saveToLocalStorage() { 69 localStorage.setItem( 70 'cart', 71 JSON.stringify({ 72 items: this.items, 73 totalAmount: this.totalAmount, 74 }) 75 ); 76 }, 77 78 // 从本地存储加载 79 loadFromLocalStorage() { 80 const savedCart = localStorage.getItem('cart'); 81 if (savedCart) { 82 try { 83 const { items, totalAmount } = JSON.parse(savedCart); 84 this.items = items; 85 this.totalAmount = totalAmount; 86 } catch (error) { 87 console.error('加载购物车数据失败:', error); 88 this.clearCart(); 89 } 90 } 91 }, 92 }, 93});
5. Pinia 模块化与相互引用
1// stores/product.js 2import { defineStore } from 'pinia'; 3 4export const useProductStore = defineStore('product', { 5 state: () => ({ 6 products: [], 7 selectedProduct: null, 8 loading: false, 9 }), 10 11 getters: { 12 inStockProducts: (state) => { 13 return state.products.filter((product) => product.inStock); 14 }, 15 16 getProductById: (state) => (id) => { 17 return state.products.find((product) => product.id === id); 18 }, 19 }, 20 21 actions: { 22 async fetchProducts() { 23 this.loading = true; 24 try { 25 // 模拟API调用 26 await new Promise((resolve) => setTimeout(resolve, 500)); 27 28 this.products = [ 29 { 30 id: 1, 31 name: '商品1', 32 price: 100, 33 inStock: true, 34 description: '这是商品1', 35 }, 36 { 37 id: 2, 38 name: '商品2', 39 price: 200, 40 inStock: false, 41 description: '这是商品2', 42 }, 43 { 44 id: 3, 45 name: '商品3', 46 price: 300, 47 inStock: true, 48 description: '这是商品3', 49 }, 50 ]; 51 } catch (error) { 52 console.error('获取产品失败:', error); 53 } finally { 54 this.loading = false; 55 } 56 }, 57 58 selectProduct(productId) { 59 this.selectedProduct = this.getProductById(productId); 60 }, 61 }, 62});
1<!-- 产品详情组件示例 --> 2<template> 3 <div class="product-detail"> 4 <div v-if="productStore.loading">加载中...</div> 5 <div v-else-if="productStore.selectedProduct" class="product-info"> 6 <h2>{{ productStore.selectedProduct.name }}</h2> 7 <p>价格: ¥{{ productStore.selectedProduct.price }}</p> 8 <p>状态: {{ productStore.selectedProduct.inStock ? '有货' : '缺货' }}</p> 9 <p>描述: {{ productStore.selectedProduct.description }}</p> 10 11 <button 12 @click="addToCart" 13 :disabled="!productStore.selectedProduct.inStock" 14 > 15 添加到购物车 16 </button> 17 </div> 18 <div v-else>请选择一个产品</div> 19 </div> 20</template> 21 22<script> 23import { useProductStore } from '@/stores/product'; 24import { useCartStore } from '@/stores/cart'; 25 26export default { 27 props: { 28 productId: { 29 type: Number, 30 required: true, 31 }, 32 }, 33 34 setup(props) { 35 const productStore = useProductStore(); 36 const cartStore = useCartStore(); // 引用另一个store 37 38 // 组件挂载时获取产品并选择当前产品 39 productStore.fetchProducts().then(() => { 40 productStore.selectProduct(props.productId); 41 }); 42 43 // 添加到购物车 44 const addToCart = () => { 45 if ( 46 productStore.selectedProduct && 47 productStore.selectedProduct.inStock 48 ) { 49 cartStore.addItem(productStore.selectedProduct); 50 alert('已添加到购物车!'); 51 } 52 }; 53 54 return { 55 productStore, 56 addToCart, 57 }; 58 }, 59}; 60</script>
vuex 与 pinia 对比
1、vuex 是 vue 官方的状态管理库,pinia 是 vue 3 官方的状态管理库 2、pinia 更简洁、更符合 vue 3 组合式 API 风格 3、pinia 支持插件系统,方便扩展功能 4、pinia 提供了更好的类型推导支持