一篇文章带你吃透Vuex3的状态管理

目录
  • 一. Vuex是什么
    • Vue全局事件总线
    • Vuex状态管理
    • 何时使用Vuex
  • 二. 纯vue组件案例
    • 计算总数案例
    • 添加人员案例
  • 三. Vuex工作原理和流程
    • 第一种工作流程
    • 第二种工作流程
    • 生活化的Vuex工作原理
  • 四. 在项目中引入Vuex
    • 安装Vuex
    • 创建store
    • 在Vue中挂载store
  • 五. Vuex的核心属性用法
    • 单一数据源(state)
    • 状态更新方式(mutations)
    • store中的计算属性(getters)
    • 异步更新状态(actions)
      • 同步增加总数
      • 异步增加总数
      • 为什么actions中处理异步
  • 六. 使用Vuex组件的完整代码
    • store仓库代码
    • 计算总数案例
    • 添加人员案例
    • Vuex实现组件间数据共享
  • 七. Vuex模块化编码(modules)
    • store模块化
    • 模块化的组件调用
      • map辅助函数
      • this.$store.xxx
  • 八. 总结

一. Vuex是什么

了解过Vuex的都知道,它是Vue官方的状态管理方案;可以对父子,祖孙以及兄弟组件之间进行通信;

除了使用Vuex,我们都知道还有一个方案能满足任何组件的之间的通信,那就是vue全局事件总线($ bus)

在数据接收组件内利用$ on绑定一个事件(eventName),第二个参数绑定一个回调函数来接受数据。

//接收数据的组件
this.$bus.$on('eventName',(value)=>{
   console.log(value) //{a:1,b:2}
})

在数据发送组件内利用$ emit 提交给绑定的事件(eventName),后面的参数为要传递的数据;

//发送数据的组件
var obj = {a:1,b:2}
this.$emit('eventName',obj)

那既然Vue全局事件总线($ bus)能够满足任何组件的之间的通信,为什么vue官方还要再创造出Vuex呢?

Vue全局事件总线

我们来看下面这个场景(利用事件总线进行数据共享)图片来源于尚硅谷

上图可以看到,A组件中data中有一个X属性,B,C,D组件也都需要,共享属性可以利用$ on 去获取到X属性;这样看起来,感觉不是很简单吗,没有什么啊,别急,这只是读取属性,那如果B,C,D需要修改呢?

其实只需要在B,C,D组件内去利用$ emit事件把修改的值发送给A组件,A组件再通过$ on去接受然后对X属性进行修改,光看文字是不是感觉已经很绕啦,也就是下图所示:图片来源于尚硅谷

红色箭头是B,C,D组件读取到共享属性X,绿色箭头是B,C,D组件修改X属性;

目前场景只是展示四个组件都需要一个共享属性X,通过读写,看上去都已经很乱啦,那如果大项目中有十几个,几十个组件都需要一个共享属性X呢,岂不是更乱;

Vuex状态管理

那如果要用Vuex实现X属性的共享呢?看下图:图片来源于尚硅谷

Vuex是独立于任何组件的一个存在,把A,B,C,D组件需要共享的属性放到Vuex中去管理,不需要共享的属性还是写在原组件内;此时A,B,C,D组件和Vuex是双向箭头,就是组件既可以通过其内置的api去读,也可以去修改,某一个组件修改共享的属性,其他组件获取的都是最新修改的数据;

何时使用Vuex

Vue事件总线其实也很方便,但是适合使用小项目中;对于大项目Vuex作为共享状态集中式管理,是最好的选择,方便使用以及维护;

那疑问来了,我也不清楚项目的大小怎么办,什么时候适合使用Vuex呢?

  • 项目中多个组件都需要使用或修改共同一个状态(多个组件共享同一个状态)

二. 纯vue组件案例

本来打算直接介绍引入Vuex的代码步骤和方法,但是为了更好的理解好对比,我先把我写的两个组件案例demo和代码给大家看一下,稍后再给大家看引入Vuex后的代码,虽然功能都一模一样,主要是对比Vuex使用前后的组件内部代码不同;

计算总数案例

导航二的计算总数案例组件:

代码如下:

<template>
    <div>
        <h3 style="marginBotton:10px;">此时导航三组件内总人数为:???</h3>
        <h3 :style="{marginBottom:'10px'}">当前计算的和为:{{count}}</h3>
        <el-select v-model.number="value" size="mini"  :style="{width:'60px'}" placeholder="0">
            <el-option
            v-for="item in options"
            :key="item.value"
            :label="item.label"
            :value="item.value">
            </el-option>
        </el-select>
        <el-button size="mini" @click="jia">+</el-button>
        <el-button size="mini" @click="jian">-</el-button>
        <el-button size="mini" @click="oddJia">和为奇数 +</el-button>
        <el-button size="mini" @click="asyncJia">等1秒后 +</el-button>
    </div>
</template>
<script>
export default {
    data(){
        return{
            count:0,
            options: [
                {value: 1,label: '1'},
                ...
            ],
            value: null
            }
    },
    methods:{
         jia(){
           this.count += this.value
         },
         jian(){
           this.count -= this.value
           if(this.count <= 0){
               this.count = 0
           }
         },
         oddJia(){
             if(this.count%2 !== 0){
                this.jia()
             }
         },
         asyncJia(){
             setTimeout(() => {
                 this.jia()
             },1000)
         }
    }
}
</script>

添加人员案例

导航三添加人员案例组件:

代码如下:

<template>
    <div>
         <el-select v-model="value" placeholder="请选择添加人员">
            <el-option
            v-for="item in options"
            :key="item.name"
            :label="item.name"
            :value="item.name">
            </el-option>
        </el-select>
        <el-button  @click="addPerson">添加</el-button>
        <el-button  @click="rest">还原</el-button>
        <el-table
            :data="filterTable"
            :border='true'
            style="width: 100%;marginTop:10px">
            <el-table-column
                align="center"
                prop="name"
                label="姓名">
            </el-table-column>
            ...
        </el-table>
        <h3 style="marginTop:10px;">此时表格总人数为:{{filterTable.length}}</h3>
        <h3 style="marginTop:10px;">此时导航二组件的计算总和为:???</h3>
    </div>
</template>
<script>
export default {
    data() {
        return {
           value:'',
           options: [
                {name: '王朝', sex:'男',age:21,hobby:'武术'},
                ...
            ],
            tableData: [
                {name: '张三', sex:'男',age:18,hobby:'唱歌'},
                {name: '李四', sex:'女',age:20,hobby:'跳舞'},
            ],
           filterTable:[]
        }
    },
    mounted(){
        this.filterTable.push(...this.tableData)
    },
    methods:{
        addPerson(){
            var isHave = true
            this.filterTable.forEach( e => {
                if(e.name == this.value)  isHave = false
            })
            if(!isHave){
                this.$message({
                    message: '人员已添加!',
                    type: 'warning',
                    duration:1000
                })
                return
            }

           var person =  this.options.filter( item => {
                return item.name == this.value
            })
            this.filterTable.push(person[0])
        },
        rest(){
           this.filterTable = this.tableData
        }
    }
}
</script>

此时两个组件是完全独立的,没有实现数据共享,所以在用到对方组件内数据的地方以 ”???“ 标记;

接下来,我们就开始一步一步使用Vuex去实现两个组件的数据共享;

三. Vuex工作原理和流程

下面是官方给的Vuex的工作原理图,如下:

如果想要很熟悉的使用Vuex,那我们就应该先了解其工作原理,明白了它内部的运转机制,那么我们就可以告别死记,就可以很熟悉流畅的编写代码;

从上图工作原理图来看Vuex中有三个最重要的核心对象,Actions,Mutations,State,那他们三个是什么关系,怎么协助运转呢,下面我们来看一下Vuex的工作流程;

第一种工作流程

为了方便了解,我给Vuex工作原理图稍微做了一些标注,图如下:

这个红色箭头就是组件使用Vuex的工作流程,配合上图,认真理解下面文字,看一下组件是怎么修改共享数据的:

  • vue组件内调用dispacth()函数,该函数内又两个参数,第一个参数是一个方法名‘key1’,第二个参数是传入的值
  • 由于的组件调用了dispatch,就会在Actions对象中去找dispatch函数传入的一个参数‘key1’;
  • 找到key1后,就会调用对应的函数;key1函数内部又调用了commit()函数。commit()函数也有两个参数,第一个参数是一个方法名‘KEY1’,第二个参数是传入的值;
  • 由于调用了commit,就会在Mutations对象中去找commit函数传入的第一个参数‘KEY1’;
  • 在Mutations对象找到‘KEY1’对应的函数后,Vuex内部调用Mutate对状态就行修改,修改状态后对修改状态的组件进行render

第二种工作流程

现在应该了解其工作的大致流程了吧,别急,还没有完,我们继续看下图:

这个应该是最完善的工作流程图,除了刚才我们介绍的最外圈的工作流程外,其实组件也可以直接去调用commit()去修改状态;那该怎么区分和使用呢?

  • Vuex内部有规定,Actions中主要写一些复杂的业务逻辑和异步处理,Mutations中主要修改状态;
  • 如果在组件内就可以拿到需要传递的value值,内部不在需要对value进行过多的业务了逻辑处理,可以直接commit()去Mutations中调用修改状态函数
  • 如果需要传递的value值要通过接口获取,然后还要进行复杂的业务逻辑,最好放到Actions的函数去处理;
  • 这样做的好处是Devtools开发工具可以准确的监控Vuex状态的变化;

生活化的Vuex工作原理

以上就是Vuex的全部工作原理以及流程;但是为了让大家更好的去记住和理解,我又模拟了下面这个场景:

Vuex就相当于一个饭店,vue组件就是顾客,Actions就是服务员,Mutations就是厨师,state就是做出来的菜和饭;我们把修改共享状态的任务改成点菜,我们可以走一下点餐流程:

通过服务员点餐的流程:

  • 首先,顾客因为不熟悉饭店菜品点菜的时候需要询问,所以要找‘服务员1’点菜,或者是通过扫描桌上外卖平台点餐
  • 其次,‘服务员1’整理好菜单后就交给了后厨‘厨师1’去做。后厨安装的有监控,以后想要知道哪个厨师做的哪个顾客的菜都能查询到;
  • 最后,厨师1做好菜,顾客就可以直接拿到;

顾客自己点餐的流程:

  • 首先,顾客很熟悉这家饭店,菜系了解,厨师也都认识这样就很简单,直接找到‘厨师1’要了几个菜;
  • 最后,厨师1做好菜,顾客就可以直接拿到;

现在应该很熟悉Vuex的工作流程了吧,很简单,学会点菜就行,哈哈,那接下来我们就要开始使用Vuex啦;

四. 在项目中引入Vuex

安装Vuex

首先,你的电脑要安装node环境,使用npm安装:

npm install vuex --save

创建store

然后在项目src文件夹下创建一个store文件夹,在其里面创建index.js:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {},
  getters:{},
  mutations: {},
  actions:{},
  modules:{}
})
export default store

在Vue中挂载store

最后在main.js文件内引入创建的store:

import Vue from 'vue'
import App from './App'
import store from './store'

new Vue({
  el: '#app',
  components: { App },
  template: '<App/>',
  store
})

五. Vuex的核心属性用法

知道一个组件怎么使用,另一个就不在话下,这里我以计算总数组件为例;

在创建store实例的时候,大家可以看到Vuex内部有五个核心属性,下面我们就从这五个核心属性入手,一步一步实现计算总数案例的所有原有功能;

删除掉计算总数组件所有逻辑代码,此时demo,如下图:

代码如下:

<template>
    <div>
        <h3 style="marginBotton:10px;">此时导航三组件内总人数为:???</h3>
        <h3 :style="{marginBottom:'10px'}">当前计算的和为:???</h3>
        <el-select v-model.number="value" size="mini"  :style="{width:'60px'}" placeholder="0">
            <el-option
            v-for="item in options"
            :key="item.value"
            :label="item.label"
            :value="item.value">
            </el-option>
        </el-select>
        <el-button size="mini" @click="jia">+</el-button>
        <el-button size="mini" @click="jian">-</el-button>
        <el-button size="mini" @click="oddJia">和为奇数 +</el-button>
        <el-button size="mini" @click="asyncJia">等1秒后 +</el-button>
    </div>
</template>
<script>
export default {
    data(){
        return{
            options: [
                {value: 1,label: '1'},
                ...
            ],
            value: null
            }
    },
    methods:{
         jia(){},
         jian(){},
         oddJia(){},
         asyncJia(){ }
    }
}
</script>

单一数据源(state)

首先是state配置,它的值是一个对象,用来存储共享状态;Vuex使用单一树原则,将所有的状态都放到这个对象上,便于定位和维护;

我们把计算总数组件的count放到Vuex中的state中,如下:

...
const store =  new Vuex.Store({
  state: {
    count:0
  },
})
...

组件中获取,可以这样:

...
 computed:{
       count(){
           return this.$store.state.count
       }
  },
...

如果组件内需要引入的共享状态属性较多,都使用this.$store.state.x的话,会比较麻烦,所Vuex给我们提供了mapState()辅助函数来获取,如下:

import {mapState} from 'vuex'
 ...
 computed:{
  //参数为对象写法
   ...mapState({
     //key值随意自定义,模板中插值也要写入自定义的key值
       count:'count'
   })
   //参数为数组写法
   // 此时组件定义的属性名要和state中定义的属性名一致
  ...mapState(['count'])
  },
  ...

以上两种获取方式,组件都可以获取到共享的count属性,并显示到页面上,如下图:

状态更新方式(mutations)

Vuex中的状态和组件中的状态不同,不能直接 state.count = ‘xx’ 这种方式去修改;Vuex修改状态的唯一方式就是提交mutation;

mutation是一个函数,第一个参数为state,它的作用就是更改state的状态;

下面我们就来定义一个mutation,在函数内去更新count这个状态:

const store =  new Vuex.Store({
  state: {
    count:0
  },
  mutations: {
    //更改count方法,type为增加
    JIA(state,count){
      state.count += count
    }
  }
})

组件中提交commit函数去触发mutattions中定义的JIA函数:

...
 methods:{
    jia(){
         this.$store.commit('JIA',this.value)
     }
 }
 ...

或者是利用最常用的mapMutations辅助函数,如下:

...
 //把获取的单选框的value值,通过参数传给mutation定义的jia函数,
 <el-button size="mini" @click="JIA(value)">+</el-button>
 ...
 import {mapMutations} from 'vuex'
 ...
  methods:{
     //对象参数中的key值为自定义的点击事件名
      ...mapMutations({JIA:'JIA'}),
      //参数为数组,此时点击事件名要和mutation中定义的增加事件名一致
       ...mapMutations(['JIA',]),
  }

以上两种方法都能实现增加功能,如下图:

store中的计算属性(getters)

有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数,这里我们就简单的把计算总数和随之增加10倍:

Getter 接受 state 作为其第一个参数,如下:

const store =  new Vuex.Store({
  state: {
    count:0
  },
  getters:{
    multipleCount(state){
      return state.count * 10
    }
  }
})

组件中可以这样去获取,如下:

<h3>当前计算的和的10倍为:{{multipleCount }}</h3>
...
 computed:{
    multipleCount(){
        return  this.$store.getters.multipleCount
    },
 },

或者利用辅助函数mapGetters获取,和mapState引入一样,如下:

import {mapGetters} from 'vuex'
...
computed:{
    //对象写法
    ...mapGetters({
        multipleCount: 'multipleCount'
    }),

    //数组写法
    //此时自定义的函数名要和getter中定义的函数名一致
   ...mapGetters(['multipleCount']),
 },

以上两种方式都可以获取到getter函数,如下图:

异步更新状态(actions)

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

最大的区别就是action中可以写一些业务逻辑和异步操作;而mutation就是简单的变更状态;

怎么说呢,其实action很像一个连接组件和mutation的中介,对于同步和异步操作在action中都能完成,它只是帮我们在actions函数中commit提交了mutations中的函数;

同步增加总数

下面我们来再一次用action来实现点击JIA函数增加总数(同步操作),如下:

const store = new Vuex.Store({<!--{C}%3C!%2D%2D%20%2D%2D%3E--> state: {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> count:0 }, mutations: {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> JIA(state,count){<!--{C}%3C!%2D%2D%20%2D%2D%3E--> state.count += count } }, actions:{<!--{C}%3C!%2D%2D%20%2D%2D%3E--> //第一个参数为context(上下文),第二个参数为传的值 jia(context,value){<!--{C}%3C!%2D%2D%20%2D%2D%3E--> console.log(context) //打印结果如下图 context.commit('JIA',value) } },})

这就是 console.log(context) 的结果,是一个对象,里面包含了commit,dispatch,getters等方法,像一个小型的srore,但是跟store不一样,我们这里就叫上下文;

既然context是个对象,如果我们只需要commit的话,也可以使用数据解构的方式写,入下图:

 actions:{
   //第一个参数为context(上下文),第二个参数为传的值
    jia({commit},value){
        commit('JIA',value)
    }
  },

组件可以分发action,通过 store.dispatch 方法触发:

methods:{
    jia(){
          this.$store.dispatch('jia', this.value)
      },
}

或者通过mapActions辅助函数来触发:

import { mapActions} from 'vuex'
...
methods:{
   //对象参数中的key值为自定义的点击事件名
   ...mapActions({jia:'jia'}),
   //数组参数
   //此时点击事件名要和action中定义的增加事件名一致
   ...mapActions(['jia']),
}

我们利用actions和mutations都可以实现JIA函数的功能使总数增加;那问题来了,我们可以调用mutations里面定义的JIA函数,为什么还要多此一举的在actions中去调用mutation,然后再去分发actions呢;

当然这里我们只是演示给大家作为参考,一般同步操作我们也不会多此一举的放入actions里面,项目中直接更新状态我们就直接在组件commit提交mutation函数就可以;

异步增加总数

由于项目没有写后台服务,是mock的数据,这里我们异步就用定时器来写,如下图:

 actions:{
    asyncJia({commit},value){
        setTimeout(() => {
          context.commit('JIA',value)
        },1000)
    }
 },

组件中分发action方法如下:

 methods:{
       asyncJia(){
           this.$store.dispatch('asyncJia',this.value)
        }
  }

一样,也可以利用mapActions函数,如下:

import { mapActions} from 'vuex'
...
methods:{
   // //对象参数中的key值为自定义的点击事件名
   ...mapActions({asyncJia:'asyncJia'}),
    //数组参数,此时点击事件名要和action中定义的增加事件名一致
   ...mapActions(['asyncJia']),
}

不管是同步和异步操作,以上方法都能实现增加总数的功能;

为什么actions中处理异步

那问题来了,为什么actions中可以处理异步操作,而不能在mutations中进行呢?

以上属于简单的一些业务逻辑,action中的定时器或者奇数判断其实在mutation中就可以完成,这样感觉action就像是多余的一样;

官方明确表示:

  • mutations里同步的意义在于,每一个mutation执行完毕之后,可得到对应的状态,方便Vuex的devtools工具可以跟踪状态的变化
  • 如果在mutations中写入异步,devtools工具就没法知道状态是什么时候更新的,所以才有了actions
  • actions用来专门处理异步,里面触发mutations,就可以很清楚的看到mutation是何时被记录下来的,并且可以立即查看对应的状态,这样异步更新也可以看到更新状态的流程;

举个例子,还有就是遇到一些复杂的业务逻辑,第二个异步操作可能会依赖第一个操作的结果;

代码如下:

actions:{
    asyncJia({commit},value){
      return new Promise((resolve,reject) => {
        setTimeout(() => {
          context.commit('JIA',value)
          resolve()
        },1000)
      })
    }
 },

组件内调用此异步增加函数后,返回结果后再等1秒调用增加函数,代码如下:

...
<el-button size="mini" @click="doubleAsync(value)">异步 + 后再等1秒后 +</el-button>
...
import {mapMutations, mapActions} from 'vuex'
...
methods:{
     ...mapMutations(['JIA','JIAN']),
      ...mapActions(['oddJia','asyncJia']),
      doubleAsync(){
          this.asyncJia(this.value).then(() => {
            setTimeout(() => {
               this.JIA(this.value)
            },1000)
          })
      }
  }

demo案例如下图:

分工明确,这样看着清晰明了,操作起来也很方便;

扩展:其实在matations中也可以实现异步操作,或者不需要mutations,直接在action中去更改state状态,页面功能也都可以正常使用并显示;但是上面说到了,如果Vuex没有操作规则,大家都随心所欲的去编写逻辑代码,devtools工具也不会正确跟踪,那样也不便于后期维护,代码一样很乱,所以我们还是听从Vuex官方推荐的标准去使用;

还有一个modules模块没有介绍,不要着急,目前我们只是实现了单个组件与Vuex关联,只是介绍了核心属性的api用法,还没有实现多组件数据共享;

下面就把项目中两个组件的详细的Vuex版本代码分享出来,作为参考。

六. 使用Vuex组件的完整代码

store仓库代码

import Vue from 'vue'
import Vuex from 'vuex'
import {message} from 'element-ui'
Vue.use(Vuex)

const store =  new Vuex.Store({
  state: {
    count:0,
    filterTable:[]
  },

  getters:{
    multipleCount(state){
      return state.count * 10
    }
  },

  mutations: {
    JIA(state,count){
      state.count += count
    },
    JIAN(state,count){
      state.count -= count
      if(state.count <= 0) state.count = 0
    },
    ADDPERSON(state,person){
      state.filterTable.push(...person)
    },
     //这个重置方法本不需要,只是为了组件使用commit
     //dispatch('restPerson')就可以实现,这里为了下面讲解知识点而用
    RESTPERSON(state,person){
        state.filterTable = []
        state.filterTable.push(...person)
     }
  },

  actions:{
    asyncJia(context,value){
      return new Promise((resolve,reject) => {
        setTimeout(() => {
          context.commit('JIA',value)
          resolve()
        },1000)
      })
    },
    oddJia({commit,state},value){
       if(state.count%2 !== 0){
        commit('JIA',value)
       }
    },
    addPerson({commit,state},obj){
      var isHave = true
      state.filterTable.forEach( e => {
          if(e.name == obj.value)  isHave = false
      })
      if(!isHave){
          message({
              message: '人员已添加!',
              type: 'warning',
              duration:1000
          })
          return
      }

      var person =  obj.options.filter( item => {
        return item.name == obj.value
      })
      commit('ADDPERSON',person)
    },
    restPerson({commit,state},value){
       state.filterTable = []
       commit('ADDPERSON',value)
    }
  },
})

export default store

计算总数案例

导航二计算总数案例组件代码,如下:

<template>
    <div>
        <h3 style="marginBotton:10px;">此时导航三组件内总人数为:???</h3>
        <h3>当前计算的和为:{{count}}</h3>
        <h3 :style="{marginBottom:'10px'}">当前计算的和的10倍为:{{multipleCount }}</h3>
        <el-select v-model.number="value" size="mini"  :style="{width:'60px'}" placeholder="0">
           ...
        </el-select>
        <el-button size="mini" @click="JIA(value)">+</el-button>
        <el-button size="mini" @click="JIAN(value)">-</el-button>
        <el-button size="mini" @click="oddJia(value)">和为奇数 +</el-button>
        <el-button size="mini" @click="asyncJia(value)">等1秒后 +</el-button>
        <el-button size="mini" @click="doubleAsync(value)">异步 + 后再等1秒后 +</el-button>
    </div>
</template>
<script>
import {mapState,mapGetters,mapMutations, mapActions} from 'vuex'
export default {
    data(){
        return{
            options: [
                {value: 1,label: '1'},
                ...
            ],
            value: null
            }
    },
    computed:{
        ...mapGetters(['multipleCount']),
        ...mapState(['count'])
    },
    methods:{
        ...mapMutations(['JIA','JIAN']),
        ...mapActions(['oddJia','asyncJia']),
        doubleAsync(){
            this.asyncJia(this.value).then(() => {
              setTimeout(() => {
                 this.JIA(this.value)
              },1000)
            })
        }
    }
}
</script>

添加人员案例

导航三添加人员案例代码,如下:

<template>
    <div>
         <el-select v-model="value" placeholder="请选择添加人员">
            <el-option
            v-for="item in options"
            :key="item.name"
            :label="item.name"
            :value="item.name">
            </el-option>
        </el-select>
        <el-button  @click="addPerson">添加</el-button>
        <el-button  @click="rest">还原</el-button>
        <el-table
            :data="filterTable"
            :border='true'
            style="width: 100%;marginTop:10px">
             ...
            </el-table-column>
        </el-table>
        <h3 style="marginTop:10px;">此时表格总人数为:{{filterTable.length}}</h3>
        <h3 style="marginTop:10px;">此时导航二组件的计算总和为:???</h3>
    </div>
</template>
<script>
export default {
    data() {
        return {
           value:'',
           options: [...],
           tableData: [...],
        }
    },
    computed:{
        filterTable(){
          return this.$store.state.filterTable
       },
    },
    mounted(){
       this.initTable()
    },
    methods:{
        initTable(){
            const person = this.filterTable.length == 0 ? this.tableData  :  this.filterTable
             this.$store.commit('RESTPERSON',person)
        },
        addPerson(){
            const obj = {
                value:this.value,
                options:this.options
            }
            this.$store.dispatch('addPerson',obj)
        },
        rest(){
           this.$store.dispatch('restPerson',this.tableData)
        }
    }
}
</script>

Vuex实现组件间数据共享

现在就算我不去介绍,大家也会用了,现在两个组件所需彼此的数据已经放到了仓库的state中,只需要直接获取就行;

那我们就把两个组件一直存在的 ”???“ 给他们获取一下值;

计算总数组件中获取添加人员组件中表格的人员总数,如下:

   ...
   <h3 style="marginBotton:10px;">
      此时导航三组件内总人数为:{{filterTable.length}}
   </h3>
   <h3>当前计算的和为:{{count}}</h3>
   ...
    computed:{
        //只需要引入state中的filterTable属性即可
        ...mapState(['count','filterTable'])
    },
   ...

添加人员组件中获取计算总数的值,如下:

 ...
 <h3 style="marginTop:10px;">
     此时表格总人数为:{{filterTable.length}}
 </h3>
 <h3 style="marginTop:10px;">此时导航二组件的计算总和为:{{count}}</h3>
  ...
    computed:{
      filterTable(){
          return this.$store.state.filterTable
       },
       count(){
          return this.$store.state.count
       },
    },
  ...

只要导航二计算组件中的计算总数值count发生变化,导航三添加人员组件内的计算总和也随之变化;

只要导航三添加人员组件表格数据发生变化,导航二计算总数组件内总人数也随之变化;如下图:

这样就是实现了组件间数据的共享,简单多了吧,你也可以像我这样去练习;

七. Vuex模块化编码(modules)

其实到目前为止,你已经基本上学会了Vuex的使用了,modules只是辅助我们把Vuex模块化,让我们的代码更清晰明了,便于维护和开发,提高发开效率;

想一下,目前我们只是有两个组件需要共享数组,看着没有问题,同样要是有十几个组件,几十个组件也需要共享数据呢,难道我们要把所有共享的状态和更改的业务逻辑都写在一个state,mutations和actions中吗,可以想到肯定很乱,怎么办,这就有了modules;

store模块化

如果要使用模块化编码的话,store中的代码编写会发生变化,组件引入的api不会没有改变,但是写法也会有所不同;

首先我们先来使store分成模块,目前我们有两个组件,每个组件的state,mutations和actions都是独立的,所以我们可以拆分两个模块:

在store文件夹下创建一个count.js文件,此文件只属于计算总数的状态和业务逻辑,最后记得暴漏出去,如下:

const countOptions = {
   //这里添加此属性,是为了后面使用map辅助函数简写时可以找到该模块
    namespaced: true,
    state: {
        count:0,
    },
    getters:{
      multipleCount(state){
         return state.count * 10
      }
    },
    mutations: {
      JIA(state,count){
          state.count += count
      },
      JIAN(state,count){
	      state.count -= count
	      if(state.count <= 0) state.count = 0
      },
    },

    actions:{
      asyncJia(context,value){
	      return new Promise((resolve,reject) => {
	          setTimeout(() => {
	            context.commit('JIA',value)
	            resolve()
	          },1000)
	      })
      },
      oddJia({commit,state},value){
	      if(state.count%2 !== 0){
	          commit('JIA',value)
	       }
      },
    },
}
export default countOptions

在store文件夹下创建一个addPerson.js文件,此文件只属于添加人员的状态和业务逻辑,最后也要暴露出去,如下:

import {message} from 'element-ui'
const addPersonOption = {
    namespaced: true,
    state: {
        filterTable:[]
    },
    mutations: {
      ADDPERSON(state,person){
          state.filterTable.push(...person)
      },
     //这个重置方法本不需要,只是为了组件使用commit
     //dispatch('restPerson')就可以实现,这里为了下面讲解知识点而用
      RESTPERSON(state,person){
        state.filterTable = []
        state.filterTable.push(...person)
      }
    },
    actions:{
      addPerson({commit,state},obj){
	       var isHave = true
	       state.filterTable.forEach( e => {
	           if(e.name == obj.value)  isHave = false
	       })
	       if(!isHave){
	           message({
	               message: '人员已添加!',
	               type: 'warning',
	               duration:1000
	           })
	           return
	       }
	       var person =  obj.options.filter( item => {
	         return item.name == obj.value
	       })
	       commit('ADDPERSON',person)
      },
      restPerson({commit,state},value){
	        state.filterTable = []
	        commit('ADDPERSON',value)
      }
    },
}
export default addPersonOption

在store/index.js文件里面引入上面两个模块组件,挂到如下modules中,如下:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

import countOptions  from './count'
import addPersonOption from './addPerson'

const store =  new Vuex.Store({
  modules:{
    countOptions,
    addPersonOption
  }
})
export default store

这样分成模块去开发,是不是,眼前一亮,清晰多啦;

模块化的组件调用

看到这里不知道你是否发现,我在计算总数组件里面用的都是map函数调用方法;在添加人员组件用的都是this.$store.xx的调用方法;之所以这样做就是为了现在使用模块化的时候给大家都全部介绍一下api在模块下的引入方式;

map辅助函数

命名空间(namespaced)+ map辅助函数的简单写法去调用,写法如下;

mapState函数的调用改为:

    computed:{
        ...mapState('countOptions',['count']),
        ...mapState('addPersonOption',['filterTable'])
    },

mapGetters函数的调用改为:

    computed:{
        ...mapGetters('countOptions',['multipleCount']),
    },

mapMutations函数的调用改为:

methods:{
     ...mapMutations('countOptions',['JIA','JIAN']),
 }

mapActions函数的调用改为:

methods:{
    ...mapActions('countOptions',['oddJia','asyncJia']),
 }

this.$store.xxx

获取模块化states方法,state后面加上模块名,如下:

computed:{
   filterTable(){
      return this.$store.state.addPersonOption.filterTable
   },
   count(){
      return this.$store.state.countOptions.count
   },
},

获取模块化mutations方法,在mutation函数名前加上 “模块名+/”,如下:

   methods:{
       initTable(){
            var person = this.filterTable.length == 0 ? this.tableData  :  this.filterTable
            this.$store.commit('addPersonOption/RESTPERSON',person)
        },
   }

为了了解获取模块化getters的调用方法,我们给Vuex的addPersonOption模块添加一个getters方法获取表格第一个人的名字,组件调用展示,代码如下:

 getters: {
      onePersonName(state){
         return state.filterTable[0].name
      }
 },
 computed:{
       onePersonName(){
           return this.$store.getters.['addPersonOption/onePersonName']
       }
  }

获取模块化actions方法,如下:

 methods:{
     addPerson(){
          var obj = {
              value:this.value,
              options:this.options
          }
          this.$store.dispatch('addPersonOption/addPerson',obj)
      },
      rest(){
          this.$store.dispatch('addPersonOption/restPerson',this.tableData)
      }
 }

再给大家看一下demo,修改后,功能正常使用!完美!,图如下:

八. 总结

好了,以上就是关于Vuex的所有知识点,因为最近又刷了一遍尚硅谷Vue的视频,为了练习和总结,就写了这篇将近18000字的关于Vuex博客,应该算是很详细,很详细啦!

到此这篇关于一篇文章带你吃透Vuex3的状态管理的文章就介绍到这了,更多相关Vuex3状态管理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解Vue中状态管理Vuex

    vuex是一个专门为vue.js设计的状态管理模式,并且也可以使用devtools进行调试. 在vuex出现之前,vue里面的状态是属于'单向数据流'.举个官网的例子: new Vue({ // state data () { return { count: 0 } }, // view template: `<div>{{ count }} </div`, // actions methods: { increment () { this.count++ } } }) 其中 state

  • 一篇看懂vuejs的状态管理神器 vuex状态管理模式

    关于vuex类的新闻最近很多,看到眼热就去查了下资料,然后扯出来一堆flux.redux.state.state之类的概念,以及大型工程必要性之类的.看官方手册也是昏昏然. 然而,我还是弄懂了!我准备从demo出发,以同样的一个最简单的demo,演示两种情况下的代码编写情况: 单纯依赖于vue.js 依赖vue.js,也使用了vuex技术 目的是通过对比引出vuex的概念.优势和劣势.也许这是目前最接地气的vuex的介绍吧:).所以无论如何在了解vuex之前,你必须懂得vue.js(好像废话:)

  • 说说如何使用Vuex进行状态管理(小结)

    1 为什么需要状态管理 一个 Vue 组件分为数据(model)与视图(view).当通过 methods 中的方法更新数据时,视图也会自动更新. message.vue <template> <div> {{message}} <button @click="changeMessage">改变内容</button> </div> </template> <script> export default

  • 浅谈Vuex的状态管理(全家桶)

    Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化.Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试.状态快照导入导出等高级调试功能. 以上是vuex的官方文档对vuex的介绍,官方文档对vuex的用法进行了详细的说明.这里就不再细讲vuex的各个用法,写这篇博客的目的只是帮助部分同学更快地理解并上手vuex.

  • 详解Vuex管理登录状态

    又仔细看了一遍vuex的文档,还是云里雾里的,不过至少明白它是一个专门管理状态的,根据数据状态的改变可以驱动视图更新,既然是这样那至少登录注册是一种状态,就用登录来做测试,学习vuex,不过话说回来,既然专门管理状态,那我至少要仔细推敲一下这个learn的学习项目有那些状态逻辑. 1.据说储存的vuex store里面的状态是临时的,右键刷新一下页面这些状态就销毁了(这是据说,请大神解惑我也没办法证实),如果是这样的话,我的用户状态user还是应该要写入sessionStorage,不然登录了的

  • 快速掌握Vue3.0中如何上手Vuex状态管理

    Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化.Vuex 也集成到 Vue 的官方调试工具 devtools,提供了诸如零配置的 time-travel 调试.状态快照导入导出等高级调试功能. 如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的.确实是如此--如果您的应用够简单,您最好不要使用 Vuex.一个简单的 store 模式就足够您所需了.但是,如果您需要构建一个中大型

  • Vue的状态管理vuex使用方法详解

    引入vuex 当访问数据对象时,一个 Vue 实例只是简单的代理访问.所以,如果有一处需要被多个实例间共享的状态,可以简单地通过维护一份数据来实现共享 const sourceOfTruth = {} const vmA = new Vue({ data: sourceOfTruth }) const vmB = new Vue({ data: sourceOfTruth }) 现在当 sourceOfTruth 发生变化,vmA 和 vmB 都将自动的更新引用它们的视图.子组件们的每个实例也会

  • 详解vuex状态管理模式

    一.前言 本次接受一个BI系统,要求是能够接入数据源-得到数据集-对数据集进行处理-展现为数据的可视化,这一个系统为了接入公司自身的产品,后端技术采用spring boot,前端采用vue+vuex+axios的项目架构方式,vuex作为vue的状态管理,是尤为重要的部分.这里,我将vuex如何运作和使用做一次总结,有错的地方,望多多提点. 二.vuex简单使用 安装vuex cnpm install vuex --save 在src目录下建立文件夹,命名为store,建立index.js 如图

  • 一篇文章带你吃透Vuex3的状态管理

    目录 一. Vuex是什么 Vue全局事件总线 Vuex状态管理 何时使用Vuex 二. 纯vue组件案例 计算总数案例 添加人员案例 三. Vuex工作原理和流程 第一种工作流程 第二种工作流程 生活化的Vuex工作原理 四. 在项目中引入Vuex 安装Vuex 创建store 在Vue中挂载store 五. Vuex的核心属性用法 单一数据源(state) 状态更新方式(mutations) store中的计算属性(getters) 异步更新状态(actions) 同步增加总数 异步增加总数

  • 一篇文章带你吃透Vue生命周期(结合案例通俗易懂)

    目录 1.vue生命周期 1.0_人的-生命周期 1.1_钩子函数 1.2_初始化阶段 1.3_挂载阶段 1.4_更新阶段 1.5_销毁阶段 2.axios 2.0_axios基本使用 2.1_axios基本使用-获取数据 2.2_axios基本使用-传参 2.3_axios基本使用-发布书籍 2.4_axios基本使用-全局配置 3.nextTick和refs知识 3.0$refs-获取DOM 3.1$refs-获取组件对象 3.2$nextTick使用 3.3$nextTick使用场景 3.

  • 一篇文章带你吃透JavaScript中的DOM知识及用法

    目录 一.前言 二.DOM框架 三.认识DOM节点 四.JS访问DOM 1.获取节点 2.改变 HTML 3.改变 CSS 4.检测节点类型 5.操作节点间的父子及兄弟关系 6.操作节点属性 7.创建和操作节点 总结 一.前言 DOM:Document Object Model(文档对象模型),定义了用户操作文档对象的接口,可以说DOM是自HTML将网上相关文档连接起来后最伟大的创新.它使得用户对HTML有了空前的访问能力,并使开发者将HTML作为XML文档来处理. 本文知识导图如下: 二.DO

  • 一篇文章带你搞定 springsecurity基于数据库的认证(springsecurity整合mybatis)

    一.前期配置 1. 加入依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>mysql</groupId> &

  • 一篇文章带你搞懂Java线程池实现原理

    目录 1. 为什么要使用线程池 2. 线程池的使用 3. 线程池核心参数 4. 线程池工作原理 5. 线程池源码剖析 5.1 线程池的属性 5.2 线程池状态 5.3 execute源码 5.4 worker源码 5.5 runWorker源码 1. 为什么要使用线程池 使用线程池通常由以下两个原因: 频繁创建销毁线程需要消耗系统资源,使用线程池可以复用线程. 使用线程池可以更容易管理线程,线程池可以动态管理线程个数.具有阻塞队列.定时周期执行任务.环境隔离等. 2. 线程池的使用 /** *

  • 一篇文章带你了解清楚Mysql 锁

    一丶为什么数据库需要锁 数据库锁设计的初衷是处理并发问题.作为多用户共享 的资源,当出现并发访问的时候,数据库需要合理地控制资源的访问规则.而锁就是用来实 现这些访问规则的重要数据结构. 根据加锁的范围,MySQL 里面的锁大致可以分成全局锁.表级锁和行锁三类 二丶全局锁&全库逻辑备份 全局锁就是对整个数据库实例加锁.全局锁的典型使用场景是,做全库逻辑备份,全库逻辑备份有以下几种方式: 1.Flush tables with read lock (FTWRL) Flush tables with

  • 一篇文章带你弄清楚Redis的精髓

    目录 一.Redis的特性 1.1 Redis为什么快? 1.2 Redis其他特性 1.3 Redis高可用 二.Redis数据类型以及使用场景 2.1 String 2.1.1 基本指令 2.1.2 应用场景 2.2 Hash 2.2.1 基本指令 2.2.2 应用场景 2.3 List 2.3.1 基本指令 2.3.2 应用场景 2.4 Set 2.4.1 基本指令 2.4.2 应用场景 2.5 ZSet(SortedSet) 2.5.1 基本指令 2.5.2 应用场景 三.Redis的事

  • 一篇文章带你使用Typescript封装一个Vue组件(简单易懂)

    一.搭建项目以及初始化配置 vue create ts_vue_btn 这里使用了vue CLI3自定义选择的服务,我选择了ts.stylus等工具.然后创建完项目之后,进入项目.使用快捷命令code .进入Vs code编辑器(如果没有code .,需要将编辑器的bin文件目录地址放到环境变量的path中).然后,我进入编辑器之后,进入设置工作区,随便设置一个参数,这里比如推荐设置字号,点下.这里是为了生成.vscode文件夹,里面有个json文件. 我们在开发项目的时候,项目文件夹内的文件很

  • 一篇文章带你搞定SpringBoot中的热部署devtools方法

    一.前期配置 创建项目时,需要加入 DevTools 依赖 二.测试使用 (1)建立 HelloController @RestController public class HelloController { @GetMapping("/hello") public String hello(){ return "hello devtools"; } } 对其进行修改:然后不用重新运行,重新构建即可:只加载变化的类 三.热部署的原理 Spring Boot 中热部

  • 一篇文章带你搞定SpringBoot不重启项目实现修改静态资源

    一.通过配置文件控制静态资源的热部署 在配置文件 application.properties 中添加: #表示从这个默认不触发重启的目录中除去static目录 spring.devtools.restart.exclude=classpath:/static/** 或者使用: #表示将static目录加入到修改资源会重启的目录中来 spring.devtools.restart.additional-paths=src/main/resource/static 此时对static 目录下的静态

随机推荐