• 作者:老汪软件技巧
  • 发表时间: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。无论选择哪种工具,都需要根据项目的具体需求来权衡利弊。如果这篇文章对你有帮助的话,可以点个赞哦!