16-购物车案例 Vuex做状态管理

1,032 阅读3分钟

创建模块和组件

模块

store文件夹下创建modules模块文件夹,在模块文件夹下分别创建cart.jsproducts.js模块,并将两个模块和store中的index.js关联起来,在index.js中将两个模块导入,并在modules属性中将两个模块挂载。


index.js

//导入
import cart from './modules/cart';
import products from './modules/products';


//挂载
  modules: {
    cart,
    products
  }

组件

因为案例并不涉及路由跳转,直接在components中创建商品组件ProductList和购物车组件ShoppingCart

后台数据

在项目目录下创建vue.config.js文件,配置devServer,重启项目,同时获取该后台数据需要发起请求,需安装axios,终端输入npm i axios -s进行安装。

注意:每次该文件进行修改都需要重启项目才能生效。


vue.config.js

const products = [{
    id: 1,
    title: 'Redmi 10',
    price: 2200,
    inventory: 12
}, {
    id: 2,
    title: 'Redmi 10 pro',
    price: 2800,
    inventory: 18
}, {
    id: 3,
    title: 'Redmi 10 max',
    price: 3200,
    inventory: 8
}]

module.exports = {
    devServer: {
        before(app, serve) {
            app.get('/api/products', (req, res) => {
                res.json({
                    results: products
                })
            })
        }
    }
}

模块和组件开发

products.js模块

//导入axios
import axios from "axios"

export default {
    namespaced: true,
    state: {
        products: []
    },
    getters: {},
    mutations: {
        //从后台拿取数据
        getProductsFromServe(state, products) {
            state.products = products;
        },
        //添加到购物车,库存减一
        decreaseProductsInventory(state, {
            id
        }) {
            //查找添加到购物车的商品的id与商铺中商品id是否相等,并对其进行库存减一
            const product = state.products.find(item => item.id === id);
            product.inventory--;
        }
    },
    actions: {
        //从后台获取数据
        async getProductsFromServe({
            commit
        }) {
            //发送请求  获取数据  提交mutations
            try {
                const res = await axios.get('/api/products');
                //console.log(res.data.results);
                const results = res.data.results;
                //提交
                commit('getProductsFromServe', results);
            } catch (error) {
                console.log(error);
            }
        }
    }
}

cart.js模块

在当前模块获取其他模块的状态,需要rootState参数,在当前模块提交到另一个模块时需要{root:true}参数

export default {
    namespaced: true,
    state: {
        cartList: [],
        count: 0
    },
    getters: {
        //获取购物车的数据
        //rootState 获取根节点对象index.js,通过它在cart模块中获取product模块中的商品信息
        //在一个模块获取另一个模块的数据时,需要rootState
        getCartList(state, getters, rootState) {
            //console.log(rootState);
            //{id,quantity} 对cartList中的每一项进行解构
            return state.cartList.map(({
                id,
                quantity
            }) => {
                //购物车中商品的id与当前商品的id是否一样
                const product = rootState.products.products.find(item => item.id === id);
                return {
                    title: product.title,
                    price: product.price,
                    quantity: quantity
                }
            })
        },
        //通过 getters 能够获取 getters 中的方法
        //计算总价
        getTotalPrice(state, getters) {
            return getters.getCartList.reduce((total, product) => {
                return total += product.price * product.quantity
            }, 0)
        }
    },
    mutations: {
        //第一次添加商品到购物车
        pushProductToCart(state, {
            id,
            quantity
        }) {
            state.cartList.push({
                id,
                quantity
            })
        },
        //购物车中已有数据,只改变当前的数量
        addCartItemQuantity(state, {
            id
        }) {
            //在购物车中查找当前添加的商品,进行数量+1
            const product = state.cartList.find(item => item.id === id);
            product.quantity++;
        }
    },
    actions: {
    	//添加商品到购物车
        addProductToCart({
            commit,
            state
        }, product) {
            //console.log(product);
            //判断是否有库存
            if (product.inventory > 0) {
                //判断是否是第一次添加
                const cartItem = state.cartList.find(item => item.id === product.id);
                //console.log(cartItem);
                //如果没有找到,则是第一次添加
                if (!cartItem) {
                    commit('pushProductToCart', {
                        id: product.id,
                        quantity: 1
                    })
                } else {
                    //购物车中已添加该商品,进行数量+1
                    commit('addCartItemQuantity', {
                        id: product.id
                    })
                }
                //库存减1,提交到products模块中的decreaseInventory方法
                //当从一个模块提交到另一个模块时,一定要加上第三个参数 {root:true}
                commit('products/decreaseProductsInventory', {
                    id: product.id
                }, {
                    root: true
                });
            }
        }
    }
}

ProductList组件

<template>
  <div>
    <h2>商铺</h2>
    <ul>
      <li>{{product}} - {{price}} - {{inventory}}</li>
      <li v-for="product in products" :key="product.id">
        {{product.title}} - {{product.price | currency}} - {{product.inventory}}
        <!-- 库存为0时,禁用按钮 -->
        <button :disabled="!product.inventory" @click="addProductToCart(product)">添加到购物车</button>
      </li>
    </ul>
  </div>
</template>

<script>
//导入辅助函数
import { mapState, mapActions } from "vuex";
  
export default {
  name: "ProductList",
  data() {
    return {
      product: "产品",
      price: "价格",
      inventory: "库存",
    };
  },
  computed: {
  //获取products模块中的products状态
    ...mapState("products", ["products"]),
  },
  created() {
    //触发products模块中的方法获取后台数据,对列表进行渲染
    this.$store.dispatch("products/getProductsFromServe");
  },
  methods: {
    //获取cart模块的addProductToCart方法
    ...mapActions("cart", ["addProductToCart"]),
  },
};
</script>

<style scoped>
</style>

ShoppingCart组件

<template>
  <div>
    <h2>我的购物车</h2>
    <ul>
      <li
        v-for="(item,index) in getCartList"
        :key="index"
      >{{item.title}} - {{item.price | currency}} x {{item.quantity}}</li>
    </ul>
    <!-- currency添加过滤器,对价格进行过滤 -->
    <h2>总价:{{getTotalPrice|currency}}</h2>
  </div>
</template>

<script>
  //导入辅助函数
import { mapGetters } from "vuex";
export default {
  name: "ShoppingCart", //组件名,注册全局组件时使用
  computed: {
    ...mapGetters("cart", ["getCartList", "getTotalPrice"]),
  },
};
</script>

<style scoped>
</style>

注册全局组件、过滤器

需要在main.js中进行注册,main.js是整个项目的入口文件。

注册全局组件

首先导入组件,然后进行注册,既可以命名也可以通过组件抛出的name属性命名。

//导入组件
import ShoppingCart from '@/components/ShoppingCart'

//全局注册组件
//Vue.component('ShoppingCart', ShoppingCart)
//使用组件抛出的组件名
Vue.component(ShoppingCart.name, ShoppingCart)

注册全局过滤器

//全局注册过滤器
Vue.filter('currency', (value) => '¥' + value)

Vuex中导入插件

在store文件夹下的index.js中导入

//导入日志插件
import createLogger from 'vuex/dist/logger'

然后在通过plugins属性进行挂载

export default new Vuex.Store({
  //日志插件  相当于集成了 Vue Devtools
  plugins: [createLogger({
    // 自动展开记录的 mutation
    collapsed: false
  })],
})