一、什么是狀態管理?為什么需要 Vuex?
1. 狀態管理的基本概念
在 Vue 應用中,狀態指的是應用中的數據。例如:
- 用戶登錄狀態
- 購物車中的商品
- 文章列表的分頁信息
狀態管理就是對這些數據的創建、讀取、更新和刪除進行有效管理。
2. 為什么需要 Vuex?
在小型應用中,我們可以通過?props?和?events?實現組件間通信。但在中大型應用中,這種方式會面臨以下問題:
- 多層級組件通信復雜:跨級組件通信需要通過中間組件層層傳遞
- 狀態共享困難:多個不相關組件需要共享同一狀態時,代碼會變得混亂
- 狀態變化不可追蹤:數據流向不清晰,調試困難
Vuex 通過集中式存儲應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化,解決了上述問題。
二、Vuex 核心概念
Vuex 的核心由以下幾個部分組成:
1.?State:應用狀態的單一數據源
State 是存儲應用狀態的對象,類似于組件中的?data
。但與組件的?data
?不同的是,Vuex 的 state 是全局共享的。
// store.js
const store = createStore({state: {count: 0,user: null,cartItems: []}
})
組件可以通過?this.$store.state
?訪問這些狀態:
<template><div><p>Count: {{ $store.state.count }}</p></div>
</template>
2.?Getters:類似于計算屬性,獲取派生狀態
Getters 用于獲取 state 經過處理后的值,類似于組件中的計算屬性。
// store.js
const store = createStore({state: {todos: [{ id: 1, text: 'Learn Vuex', completed: true },{ id: 2, text: 'Build an app', completed: false }]},getters: {completedTodos(state) {return state.todos.filter(todo => todo.completed);}}
})
組件中使用:
<template><div><p>Completed todos: {{ $store.getters.completedTodos.length }}</p></div>
</template>
3.?Mutations:更改 state 的唯一方法
Mutations 是唯一可以修改 state 的地方,并且必須是同步的。
// store.js
const store = createStore({state: {count: 0},mutations: {increment(state) {state.count++;},incrementBy(state, payload) {state.count += payload;}}
})
組件中通過?commit
?觸發 mutation:
<script>
export default {methods: {handleIncrement() {this.$store.commit('increment'); // 觸發 increment mutationthis.$store.commit('incrementBy', 5); // 傳遞參數}}
}
</script>
4.?Actions:處理異步操作
Actions 用于處理異步操作(如 API 請求),完成后通過?commit
?提交 mutation。
// store.js
const store = createStore({state: {user: null,loading: false},mutations: {SET_USER(state, user) {state.user = user;},SET_LOADING(state, loading) {state.loading = loading;}},actions: {async fetchUser({ commit }) {commit('SET_LOADING', true);try {const response = await fetch('/api/user');const user = await response.json();commit('SET_USER', user);} catch (error) {console.error('Failed to fetch user', error);} finally {commit('SET_LOADING', false);}}}
})
組件中通過?dispatch
?觸發 action:
<script>
export default {methods: {async loadUser() {await this.$store.dispatch('fetchUser');}}
}
</script>
5.?Modules:模塊化管理大型應用
當應用變得復雜時,可以將 store 分割成多個模塊,每個模塊有自己的 state、mutations、actions 和 getters。
// store/modules/cart.js
export default {namespaced: true, // 啟用命名空間state: {items: []},mutations: {ADD_ITEM(state, item) {state.items.push(item);}},actions: {addToCart({ commit }, item) {commit('ADD_ITEM', item);}},getters: {itemCount(state) {return state.items.length;}}
}
在根 store 中注冊模塊:
// store/index.js
import { createStore } from 'vuex'
import cart from './modules/cart'
import user from './modules/user'export default createStore({modules: {cart,user}
})
三、Vuex 工作流程:單向數據流
Vuex 采用單向數據流的設計理念,所有狀態變更都遵循固定的流程:
- 視圖觸發 Action:組件通過?
dispatch
?觸發 action - Action 處理異步邏輯:如 API 請求、定時器等
- Action 提交 Mutation:完成后通過?
commit
?提交 mutation - Mutation 修改 State:mutation 是唯一允許修改 state 的地方
- State 變化觸發視圖更新:Vue 的響應式系統會自動更新所有依賴該 state 的組件
組件(dispatch) → Action(commit) → Mutation(modify) → State → 組件更新
四、實戰案例:使用 Vuex 構建購物車應用
下面通過一個簡單的購物車應用來演示 Vuex 的實際應用。
實現效果:
vuex實現購物車
1. 項目結構
src/├── store/│ ├── index.js # 根 store│ └── modules/│ └── cart.js # 購物車模塊├── components/│ ├── ProductList.vue # 商品列表│ ├── Cart.vue # 購物車│ └── Navbar.vue # 導航欄└── App.vue
2. 創建購物車模塊
// store/modules/cart.js
export default {// 設置命名空間,以便在多個模塊中避免狀態、getters、mutations和actions的命名沖突namespaced: true,// 定義模塊的狀態state: {// 購物車中的商品項items: []},// 定義獲取狀態的getter函數getters: {// 計算購物車中的商品數量itemCount: state => state.items.length,// 計算購物車中商品的總價totalPrice: state => {return state.items.reduce((total, item) => {return total + item.price * item.quantity;}, 0);}},// 定義直接修改狀態的mutation函數mutations: {// 添加商品到購物車ADD_ITEM(state, product) {// 查找購物車中是否已存在該商品const existingItem = state.items.find(item => item.id === product.id);if (existingItem) {// 如果存在,增加該商品的數量existingItem.quantity++;} else {// 如果不存在,將該商品添加到購物車中,并設置數量為1state.items.push({ ...product, quantity: 1 });}},// 從購物車中移除商品REMOVE_ITEM(state, productId) {// 過濾掉要移除的商品state.items = state.items.filter(item => item.id !== productId);},// 清空購物車CLEAR_CART(state) {// 將購物車中的商品項設置為空數組state.items = [];}},// 定義異步操作和提交mutation的action函數actions: {// 將商品添加到購物車的actionaddToCart({ commit }, product) {// 提交ADD_ITEM的mutationcommit('ADD_ITEM', product);},// 從購物車中移除商品的actionremoveFromCart({ commit }, productId) {// 提交REMOVE_ITEM的mutationcommit('REMOVE_ITEM', productId);},// 清空購物車的actionclearCart({ commit }) {// 提交CLEAR_CART的mutationcommit('CLEAR_CART');}}
};
3. 注冊模塊到根 store
// store/index.js
import { createStore } from 'vuex';
import cart from './modules/cart';export default createStore({modules: {cart}
});
4. 創建商品列表組件
<!-- components/ProductList.vue -->
<template><div class="product-list"><h2>商品列表</h2><div class="products"><div v-for="product in products" :key="product.id" class="product"><img :src="product.image" alt="Product" /><h3>{{ product.name }}</h3><p>{{ product.price }} 元</p><button @click="addToCart(product)">加入購物車</button></div></div></div>
</template><script>
export default {data() {return {products: [{ id: 1, name: 'iPhone 13', price: 6999, image: 'https://picsum.photos/200/300?random=1' },{ id: 2, name: 'MacBook Air', price: 9999, image: 'https://picsum.photos/200/300?random=2' },{ id: 3, name: 'iPad Pro', price: 7999, image: 'https://picsum.photos/200/300?random=3' }]};},methods: {addToCart(product) {this.$store.dispatch('cart/addToCart', product);alert(`${product.name} 已添加到購物車`);}}
};
</script>
5. 創建購物車組件
<!-- components/Cart.vue -->
<template><div class="cart"><h2>購物車</h2><div v-if="cartItems.length === 0" class="empty-cart">購物車為空</div><div v-else><ul><li v-for="item in cartItems" :key="item.id" class="cart-item"><img :src="item.image" alt="Product" /><div class="item-info"><h3>{{ item.name }}</h3><p>{{ item.price }} 元 x {{ item.quantity }}</p><button @click="removeFromCart(item.id)">移除</button></div></li></ul><div class="cart-summary"><p>總計: {{ totalPrice }} 元</p><button @click="clearCart">清空購物車</button></div></div></div>
</template><script>
export default {computed: {cartItems() {return this.$store.state.cart.items;},totalPrice() {return this.$store.getters['cart/totalPrice'];}},methods: {removeFromCart(productId) {this.$store.dispatch('cart/removeFromCart', productId);},clearCart() {this.$store.dispatch('cart/clearCart');}}
};
</script>
6. 創建導航欄組件(顯示購物車數量)
<!-- components/Navbar.vue -->
<template><nav class="navbar"><div class="container"><a href="#" class="brand">Vuex 購物車</a><div class="cart-icon"><i class="fas fa-shopping-cart"></i><span class="cart-count">{{ cartItemCount }}</span></div></div></nav>
</template><script>
export default {computed: {cartItemCount() {return this.$store.getters['cart/itemCount'];}}
};
</script>
7. 在 App.vue 中組合所有組件
<!-- App.vue -->
<template><div id="app"><Navbar /><div class="container"><ProductList /><Cart /></div></div>
</template><script>
import Navbar from './components/Navbar.vue';
import ProductList from './components/ProductList.vue';
import Cart from './components/Cart.vue';export default {components: {Navbar,ProductList,Cart}
};
</script><style>
/* 全局樣式 */
body {font-family: Arial, sans-serif;margin: 0;padding: 0;
}.container {max-width: 1200px;margin: 0 auto;padding: 20px;
}.navbar {background-color: #333;color: white;padding: 10px 0;
}.navbar .container {display: flex;justify-content: space-between;align-items: center;
}.brand {font-size: 24px;text-decoration: none;color: white;
}.cart-icon {position: relative;cursor: pointer;
}.cart-count {position: absolute;top: -10px;right: -10px;background-color: red;color: white;border-radius: 50%;width: 20px;height: 20px;display: flex;justify-content: center;align-items: center;font-size: 12px;
}.product-list {margin-bottom: 40px;
}.products {display: grid;grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));gap: 20px;
}.product {border: 1px solid #ddd;padding: 15px;border-radius: 5px;text-align: center;
}.product img {max-width: 100%;height: 200px;object-fit: cover;margin-bottom: 10px;
}.product button {background-color: #4CAF50;color: white;border: none;padding: 10px 15px;cursor: pointer;border-radius: 5px;
}.product button:hover {background-color: #45a049;
}.cart-item {display: flex;align-items: center;border-bottom: 1px solid #ddd;padding: 15px 0;
}.cart-item img {width: 80px;height: 80px;object-fit: cover;margin-right: 15px;
}.item-info {flex: 1;
}.item-info button {background-color: #f44336;color: white;border: none;padding: 5px 10px;cursor: pointer;border-radius: 3px;
}.item-info button:hover {background-color: #d32f2f;
}.cart-summary {margin-top: 20px;text-align: right;
}.cart-summary button {background-color: #333;color: white;border: none;padding: 10px 15px;cursor: pointer;border-radius: 5px;
}.cart-summary button:hover {background-color: #555;
}.empty-cart {padding: 20px;text-align: center;color: #666;
}
</style>
五、Vuex 高級技巧
1. 使用輔助函數簡化代碼
Vuex 提供了?mapState
、mapGetters
、mapMutations
?和?mapActions
?輔助函數來簡化組件中的代碼。
<template><div><p>Count: {{ count }}</p><button @click="increment">+</button></div>
</template><script>
import { mapState, mapMutations } from 'vuex';export default {computed: {...mapState(['count'])},methods: {...mapMutations(['increment'])}
}
</script>
2. 嚴格模式
在開發環境中啟用嚴格模式,確保所有狀態變更都通過 mutations。
// store/index.js
export default createStore({strict: process.env.NODE_ENV !== 'production'
});
3. 插件機制
Vuex 插件是一個函數,接收 store 作為唯一參數,可以用于記錄日志、持久化存儲等。
// store/plugins/logger.js
export default function logger(store) {store.subscribe((mutation, state) => {console.log('Mutation:', mutation.type);console.log('Payload:', mutation.payload);console.log('State after mutation:', state);});
}// store/index.js
import logger from './plugins/logger';export default createStore({plugins: [logger]
});
4. 狀態持久化
使用?vuex-persistedstate
?插件將 state 持久化到本地存儲。
npm install vuex-persistedstate
// store/index.js
import createPersistedState from 'vuex-persistedstate';export default createStore({plugins: [createPersistedState()]
});
六、Vuex 常見問題與解決方案
1. 何時使用 Vuex?
- 多組件共享狀態
- 組件間通信復雜
- 狀態需要被多個視圖監聽
- 中大型應用
2. 與 Vue Router 結合使用
在路由導航守衛中訪問 Vuex 狀態:
router.beforeEach((to, from, next) => {if (to.meta.requiresAuth && !store.state.user) {next('/login');} else {next();}
});
3. 性能優化
- 避免在大型列表中頻繁修改 state
- 使用?
mapState
?和?mapGetters
?緩存計算結果 - 對大型數據使用?
Vue.set()
?或?store.replaceState()