- 作者:老汪软件技巧
- 发表时间:2024-08-20 15:04
- 浏览量:
前文:浅析Vuex——快速入门复杂项目的数据流管理还在为组件间的通信烦恼吗?本文将介绍一种在大型项目中常用的管理方案——Vue - 掘金 ()
在大型项目的开发中,会有很多组件间需要数据传输,而如果只是用父子组件通信的话会十分麻烦。因此便有了Vuex。Vuex 作为 Vue.js 的官方状态管理库,通过单一状态树、模块化管理、严格的数据流动控制等设计模式,提供了高效且可预测的状态管理方案。本文将深入探讨 Vuex 的核心设计模式,并通过购物车管理的示例,展示如何利用 Vuex 的四个关键部分:state、getter、mutation 和 action 来实现复杂状态的管理。
Vuex 的核心设计模式单一状态树
Vuex 的核心设计思想之一是 单一状态树。在单一状态树中,应用的所有状态被集中存储在一个对象中,这使得状态管理的可控性和可预测性大大增强。单一状态树可以视为一棵树,每个状态节点(node)对应于树上的一个分支或叶子。
通过单一状态树,应用的所有共享状态都能以一种结构化且一致的方式进行管理。这样一来,数据流动变得更加透明,调试也更加容易。所有的状态变更都会记录在同一棵树上,使得状态追踪和时间旅行调试成为可能。
示例:在购物车项目中,我们可以将所有与购物车相关的状态(例如,商品列表、库存、已添加商品等)存储在单一状态树的 state 对象中:
const state = {
products: [],
cart: []
};
模块化管理
尽管 Vuex 提倡使用单一状态树,但当应用规模增大时,单一状态树可能会变得难以维护。为了解决这一问题,Vuex 提供了模块化管理的能力。通过模块化,每个模块都可以包含自己的 state、mutation、action 和 getter,从而实现对复杂状态的分片管理。
模块化管理允许开发者将状态树分解为多个子模块,每个模块管理自己的一部分状态。模块可以嵌套,形成树状结构,并且支持命名空间(namespaced),以避免不同模块间的命名冲突。
示例:在我们的购物车项目中,可以将商品管理和购物车管理拆分为两个独立的模块,每个模块独立管理其相关的状态和业务逻辑。
import { createStore } from 'vuex';
// vuex 比 pinia 更复杂,中央仓库的概念 store 单例 单一状态树
import cart from './modules/cart'
import products from './modules/products'
// 仓库
// 分子仓
export default createStore({
// 全局状态 简单 数据一多就麻烦了。
state:{
count:0
},
// 模块化
modules: {
cart, // 购物车状态
products // 商品状态
},
});
Vuex 的四个核心部分State:状态管理
state 是 Vuex 的基础部分,用于存储应用的所有共享状态。在单一状态树中,state 作为树的根节点(root node),包含了应用中所有需要共享的数据。通过将状态集中在一个地方,Vuex 提供了数据的一致性和可控性。
在我们的购物车示例中,state 中存储了所有商品和购物车的状态信息
Getter:状态派生
getter 用于从 state 中派生出更多的数据或计算结果。类似于 Vue 的计算属性(computed properties),getter 在状态改变时会自动重新计算,并可以缓存计算结果。
在购物车项目中,我们可以使用 getter 计算购物车中的商品总价:
const state = {
items: []
}
const getters = {
cartProducts:(state, getters, rootState) => {
return state.items.map(({id, quantity}) => {
const product =
rootState.products.all.find(product => product.id === id)
return {
id: product.id,
title: product.title,
price: product.price,
quantity
}
})
},
cartTotalPrice:(state,getters) => {
// 数组消灭 参数1 上一个的返回值
return getters.cartProducts.reduce((total, product) => {
return total + product.price * product.quantity
}, 0)
}
}
Mutation:状态变更
mutation 是 Vuex 中唯一允许直接修改 state 的方式。所有的状态变更必须通过 mutation 进行,从而保证了状态变更的可预测性和可追踪性。每个 mutation 都有一个类型(type)和回调函数,回调函数负责执行具体的状态修改。
在购物车示例中,我们通过 mutation 来减少商品库存和向购物车添加商品:
product.js
const mutations = {
// vuex mutations api 第一个参数是state
// products 是传来的参数
setProducts(state, products){
state.all = products
},
decrementProductInventory(state, {id}){
const product = state.all.find(product => product.id === id)
console.log(product.title);
product.inventory--
}
}
cart.js
const mutations = {
pushProductToCart(state,{id}){
state.items.push({
id,
quantity:1
})
},
incrementItemQuantity(state, cartItem){
cartItem.quantity++
}
}
Action:异步操作
action 类似于 mutation,但它可以包含异步操作。action 通过 dispatch 方法来触发,并最终通过 commit 提交 mutation 来修改状态。action 的主要作用是处理业务逻辑,尤其是在需要与外部 API 交互时。
在购物车示例中,action 用于处理用户添加商品到购物车的逻辑:product.js
const actions = {
// api 请求 =》 提交mutation
getAllProducts({commit}){
// commit ? vuex 给 actions 可以 commit mutations 的API
API.getProducts((products) =>{
console.log(products);
commit('setProducts', products)
})
}
}
cart.js
const actions = {
addProductToCart({ commit, state } ,product){
if(product.inventory > 0){
const cartItem = state.items.find(item =>item.id === product.id)
if(!cartItem){
// 新添加到购物车的
commit('pushProductToCart', {id: product.id })
}
else {
commit('incrementItemQuantity', cartItem)
}
commit('products/decrementProductInventory',{id: product.id},{root: true})
}
}
}
实现效果
最后,我们将在 Vue 组件中使用 Vuex 的状态和方法来构建购物车功能。
ProductList.vue
<template>
<ul>
<li v-for="product in products " :key="product.id" >
{{ product.title }} - {{ product.price }}
<br>
<button
@click="addProductToCart(product)"
:disabled="!product.inventory"
>
Add to Cart
button>
li>
ul>
template>
<script setup>
import { computed ,onMounted } from 'vue';
import { useStore } from 'vuex';
const store = useStore(); // hooks 函数
const products = computed(() => store.state.products.all);
onMounted(() => {
store.dispatch('products/getAllProducts');
});
const addProductToCart = (product) => {
// 修改 dispatch action -> commit mutation 触发
// 共享 + 数据状态的正确性
// inventory - 1
// cart + 1
store.dispatch('cart/addProductToCart', product);
}
script>
<style lang="sass" scoped>
style>
ShoppingCart.vue
<template>
<div>
<h2>Shopping Carth2>
<p v-show="!products.length">
<i>Please add some product to carti>
p>
<ul>
<li v-for="product in products" :key="product.id">
{{ product.title }} - {{ product.price }} - {{ product.quantity }}
li>
ul>
<p>
Total: {{ total }}
p>
div>
template>
<script setup>
import {computed} from 'vue'
import {useStore} from 'vuex'
const store = useStore();
const products = computed(() => store.getters['cart/cartProducts'] );
const total = computed(() => store.getters['cart/cartTotalPrice'] );
script>
<style lang="scss" scoped>
style>
与 Pinia 的对比
Vuex 和 Pinia 都是 Vue.js 的状态管理工具,但它们在设计理念和使用方式上有所不同。Vuex 强调严格的单一状态树和 mutation 的使用,而 Pinia 则更灵活,取消了 mutation 的概念,并通过函数式编程的方式实现状态管理。
严格的数据流控制
Vuex 的数据流通过 state -> getter -> mutation -> action 形成一个严格的单向数据流。任何状态的变更都必须通过 mutation 来完成,这种严格性使得状态管理更加可控和可预测。而 Pinia 则允许在 store 中直接修改状态,提供了更大的灵活性。
模块化与命名空间
Vuex 的模块化管理通过 modules 和 namespaced 来实现,而 Pinia 的模块化则依赖于 defineStore 函数的调用。相比之下,Vuex 的模块化设计更为传统和结构化,而 Pinia 则更加灵活和易于理解。
总结
Vuex 通过单一状态树、模块化管理和严格的数据流控制,提供了一种高度可控且可扩展的状态管理方案。在大型应用中,Vuex 的设计模式有助于保持状态的一致性和数据的可预测性。在实际开发中,Vuex 的 state、getter、mutation 和 action 各司其职,共同构成了 Vuex 的核心数据流管理机制。相比之下,Pinia 则更为灵活,但在数据管理的严格性上略逊于 Vuex。无论选择哪种工具,都需要根据项目的具体需求来权衡利弊。如果这篇文章对你有帮助的话,可以点个赞哦!