Vuex

Vue状态管理Vuex

Posted by Jeremy on August 24, 2018

前言

如果你使用vue开发项目却没有用vuex那么项目开发成本会变高且慢,简单的项目可以不推荐使用vuex

什么是VUEX

官网解释的很清楚:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

自我理解,vuex是用来管理组件之间的相互通信。 组件之间的通信有几种方式,父子组件通信可以通过props传值,非父子组件可以通过中间事件进行bus进行通信; 项目变复杂时使用上面的方式会感到很无力,vuex此时就是最好的选择!

从一个例子开始

用vuex实现简单购物车的功能

1.首先来看目录结构(详细的不再赘述)

我们通过三个组件来实现购物车的基本功能,product组件用来展示商品信息(UI引用element-ui) product组件代码

<template>
    <div class="prodcut">
        <h4 style="text-align: center">商品信息</h4>
        <el-table style="width: 900px;margin: 0 auto":data="shoplist">
            <el-table-column prop="id" label="id" width="180"></el-table-column>
            <el-table-column prop="name" label="名称" width="180"></el-table-column>
            <el-table-column prop="price" label="价格"></el-table-column>
            <el-table-column label="操作">
                <template slot-scope="scope">
                    <el-button type="text" size="small">添加购物车</el-button>
                </template>
            </el-table-column>
        </el-table>
    </div>
</template>
<script>
export default {
  name: 'product',
  data() {
    return {
      shoplist: [{
              id: 11,
              name: '大鼓米线',
              price: 27,
          }, {
              id: 22,
              name: '卤道卤香',
              price: 16
          }, {
              id: 33,
              name: '谷田稻香',
              price: 24
          }, {
              id: 44,
              name: '米饭',
              price: 2
          }],
    };
  },
};
</script>

cart组件展示添加购物车商品信息

<template>
    <div class="cart">
        <h4 style="text-align: center">已选商品</h4>
        <el-table style="width: 900px;margin: 0 auto"
                  :data="cartProducts"
        >
            <el-table-column
                    prop="id"
                    label="id"
                    width="180">
            </el-table-column>
            <el-table-column
                    prop="name"
                    label="名称"
                    width="180">
            </el-table-column>
            <el-table-column
                    prop="price"
                    label="价格">
            </el-table-column>
            <el-table-column
                    prop="num"
                    label="数量">
            </el-table-column>
            <el-table-column
                    label="操作">
                <template slot-scope="scope">
                    <el-button type="text" size="small">删除</el-button>
                </template>
            </el-table-column>
        </el-table>
    </div>
</template>
<script>
export default {
  name: 'cart',
  data() {
    return {
      cartProducts: [],
    };
  },
};
</script>

info组件展示总价格和总数量

<template>
    <div class="info">
        <el-row style="width: 900px;margin: 15px auto 0;text-align: center;line-height: 40px;">
            <el-col :span="8" style="background-color: #ccc">总数:</el-col>
            <el-col :span="8" style="background-color: #ccc">总价:</el-col>
            <el-col :span="8"><el-button type="danger" plain style="width: 100%">清空购物车</el-button></el-col>
        </el-row>
    </div>
</template>

<script>
    export default {
        name:'info',
        data(){
            return {}
        },
    }
</script>

然后在app.vue中调用上面三个组件

<template>
  <div id="app" style="height: 100%">
    <h3 style="text-align: center">Vuex购物车demo</h3>
    <!-- 商品列表 -->
    <product></product>
    <!-- 购物车列表 -->
    <cart></cart>
    <!-- 统计 -->
    <info></info>
  </div>
</template>


<script>
    import product from './components/product';
    import cart from './components/cart';
    import info from './components/info';

    export default {
        name: 'app',
        components: {
            product,
            cart,
            info
        }
    }
</script>

store官方目录结构如图

本例store目录—-store/index.js

// 组装模块并导出 store 的地方
import Vue from 'vue';
import Vuex from 'vuex';

import cart from './modules/cart';

Vue.use(Vuex);

export default new Vuex.Store({
    modules: {
        cart,
    },
    // strict: process.env.NODE_ENV !== 'production', // 严格模式
});

store/modules/cart.js

//初始化数据(可以看作是vue实例化中的data)
const state = {};

//getter 抛出去的数据(可以看作vue实例化中的计算属性)
const getters = {};

//action 异步的操作(类似于 mutation,但提交的是 mutation)
const actions = {};

//mutation(类似于事件,必须是同步操作)
const mutations = {};

export default {
  state,
  mutations,
  actions,
  getters,
};

2.目录结构完成接下来就要实现功能

来看product组件中的功能,需要实现点击添加购物车的功能

首先将product中的shoplist移到store中的cart.js中

修改 cart.js 中的state

const state = {
    //商品列表
    shoplist: [{
                      id: 11,
                      name: '大鼓米线',
                      price: 27,
                  }, {
                      id: 22,
                      name: '卤道卤香',
                      price: 16
                  }, {
                      id: 33,
                      name: '谷田稻香',
                      price: 24
                  }, {
                      id: 44,
                      name: '米饭',
                      price: 2
    }],

      //添加到购物车的商品(已选商品)
      added:[]
}

我们需要将shoplist从state 中派生出出来,需要用到getters(如果很多组件共享状态不需要每个组件都复制一个方法)

//通过方法访问
const getters = {
    //商品列表
    shoplist:state => state.shoplist,
 }

如何在组件中拿到shoplist,可以直接通过 store.getters.shoplist获取,官方提供了一种更为 简便的方法mapGetters 辅助函数(getters很多的情况下优势就会体现)mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性

//引入mapGetters
import {mapGetters} from "vuex";
    export default {
        name: 'product',
        data() {
            return {}
        },
        //第一种方法使用对象展开运算符将
        // computed: {
        //     ...mapGetters(['shoplist'])
        // },
        //第二种方法使用对象形式
        computed:mapGetters({
            shoplist:'shoplist'
        }),  
    }

现在需要在product.vue中实现添加购物车的操作

 <el-button type="text" size="small" @click='addToCart(scope.row)'>添加购物车</el-button>

在cart.js中的actions编写addToCart方法

const actions = {
    //添加购物车
    addToCart({commit},product){
        commit('add',{
            id:product.id
        })
    },
}

actions提交的是mutation所以从action分发出来的方法需要mutation’接收’

const mutations = {
//添加到购物车操作
add(state,{id}){  //解构id         
let record = state.added.find(n=>n.id == id); 
    if(!record){
        state.added.push({
            id,
            num:1
        })
    }else {
        record.num++
    }
    console.info(record,state.added)
  },
}

同样组件引用action中的方法有mapActions的辅助函数

import {mapGetters,mapActions} from "vuex";
        export default {
            name: 'product',
            data() {
                return {}
            },
            //第一种方法使用对象展开运算符将
            computed: {
                ...mapGetters(['shoplist'])
            },
            //第二种方法使用对象形式
            //computed:mapGetters({
            //    shoplist:'shoplist'
            //}), 
            methods: {
                ...mapActions(['addToCart'])
            }
        }

点击添加购物车需要将数据传到cart组件中,现在修改cart.js文件

const getters = {
    //商品列表
    shoplist:state => state.shop_list,
    //购物车的列表
    cartProducts:state=>{
        return state.added.map(({id,num})=>{ //在actions中只有id和num的字段
            //在原始数据数据上面进行刷选,这里通过id来匹配
            let product = state.shop_list.find(n=>n.id == id)
            return {
                ...product,
                num
            }
        })
    },
}, cart组件拿到product组件添加的数据

import { mapGetters} from "vuex";

    computed: {
        ...mapGetters(['cartProducts’])
},

至此,我们已经实现添加将商品添加到购物车的功能,接下来需要实现删除功能。 在cart组件中添加delProduct方法

<el-button type="text" size="small" @click="delProduct(scope.row)">删除</el-button>

同样在cart.js中分发action

const actions = {
    //添加到购物车操作
    addToCart({commit},product){
        commit('add',{
            id:product.id
        })
    },
    //删除购物车的指定的商品
    delProduct({commit},product){
        commit('del',product)//commit del的方法
    }
}

mutations接收

const mutations = {
    //添加到购物车操作
    add(state,{id}){
        let record = state.added.find(n=>n.id == id);
        if(!record){
            state.added.push({
                id,
                num:1
            })
        }else {
            record.num++
        }
    },
    //删除购物车的指定的商品
    del(state,product){
        state.added.forEach((n,i)=>{
            if(n.id == product.id){
                //找到下标值
                state.added.splice(i,1)
            }
        })
    },
}

cart组件调用

import { mapGetters,mapActions} from "vuex";

 methods:{
       ...mapActions(['delProduct'])
 }

购物车添加删除功能已全部实现,我们还需要实现计算商品的总数,总价以及清空购物车的功能

在getters中操作

const getters = {
    //商品列表
    shoplist:state => state.shop_list,
    //购物车的列表
    cartProducts:state=>{
        return state.added.map(({id,num})=>{
            let product = state.shop_list.find(n=>n.id == id)
            return {
                ...product,
                num
            }
        })
    },
    //计算总价
    totalPrice:(state,getters)=>{  //getter中可以调用getter里面的方法,文档有介绍
        let total = 0;
        getters.cartProducts.forEach(n=>{
            total += n.price * n.num
        })
        return total;
    },
    //计算总数量
    totalNum:(state,getters)=>{
        let total = 0;
        getters.cartProducts.forEach(n=>{
            total += n.num
        })
        return total;
    },
}  

在info组件中调用

import { mapGetters } from 'vuex'
 computed:{
    ...mapGetters(['totalPrice','totalNum'])
 }

清空购物车,直接将added数组清空即可

info组件中添加方法

    <el-button type="danger" plain style="width: 100%" @click="clearAllCart">清空购物车</el-button>

同样需要在action中分发

const actions = {
    //添加到购物车操作
    addToCart({commit},product){
        commit('add',{
            id:product.id
        })
    },
    //清除购物车
    clearAllCart({commit}){
        commit('clearAll’) //分发一个clearAll事件,不带参数进行
    },
}

mutations接受

const mutations = {
    //添加到购物车操作
    add(state,{id}){
        let record = state.added.find(n=>n.id == id);
        if(!record){
            state.added.push({
                id,
                num:1
            })
        }else {
            record.num++
        }
        // console.info(record)
    },
    //清除购物车
    clearAll(state){
        state.added = []
    },
}

info组件中调用

import { mapGetters, mapActions } from 'vuex'

methods: {
    ...mapActions(['clearAllCart'])
},

Vuex实现简单购物车功能已实现,vuex多使用几次就可以理通各个模块所负责的功能

线上demo地址

VuexDemo