一、什么是VUEX

    组件化是VUE的核心功能,组件间的数据共享是我们开发过程中经常遇到的情况,在组件较少的情况下,可以通用公共变量等解决,一旦应用中组件数量大,且组件间相关嵌套,关系复杂,想要维护好这些公共变量,将是一件非常痛苦的事。

   了解spring的同学会熟悉IOC的概念,初始化时,为相关的类创建全局唯一的单例对象,通过注入的方式,统一管理,处处使用。VUEX要做的事如spring较类似,简单的说,就是将这些共享的数据或状态,构建成全局的单例模式进行集中管理。

总体来说,vuex的使用非常简单,主要了解以下几个概念

  • State
  • Mutations
  • Actions
  • Getters

下面我们结合项目实践,介绍下这几个概念以及使用。 

二、VUEX环境搭建

首先需要安装vuex的环境。

npm install vuex --save

采用vue-cli手脚架构建vuexdemo项目(参考VUE探索第二篇-手脚架(vue-cli))

在main.js中引入vue

import Vue from 'vue'
import App from './App'
import router from './router'
import Vuex from 'vuex'
Vue.use(Vuex);

我们模拟微信的聊天过程,如下:

VUE探索第四篇-VUEX-LMLPHP

三、State

State是vuex的单一状态树,保存各组件共享的数据源,它将会包含在store实例中。

我们来分析下这个聊天界面,"下里巴人"和"阳春白雪"分别创建两个聊天界面组件DialogA和DialogB,两者的对话保存到变量msg中,而变量msg作为共享数据源由state维护。

VUE探索第四篇-VUEX-LMLPHP

main.js中创建store实例,并在根实例中注册store。

const store = new Vuex.Store({
  state:{
	msg:"",
  }
})

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,//注册store
  components: { App },
  template: '<App/>'
})

接下来我们就在聊天界面中使用变量msg保存和展示对话。

先创建template,分成三部分,对话展示区域,消息输入框以及发送按钮。

<template>
  <div class="hello">
    <!--展示区域-->
    <div class="dialog" v-html="$store.state.msg">
    </div>
    <!--消息输入框-->
    <input type="" name="dialog" v-model="sendMessage" placeholder="请输入消息" style="width:200px">
    <!--发送按钮-->
    <button  @click="send_msg"  >发送</button>
   </div>
</template>

对话框展示区域,使用v-html绑定state的msg状态变量。

输入消息后,点击发送,响应send_msg方法,获取输入的消息文本this.sendMessage,并赋值给msg(注意组件中需要使用this.$store.state.msg,才能访问到变量)。

  methods:{
   send_msg:function(){
    if(this.sendMessage==""){
        return
    }
    this.$store.state.msg+="<div class='showmsg'><img src='/static/mail.jpg'></img> "+this.sendMessage+"</div>";
    this.sendMessage="";

   }
  }

然后赋值给revMessage,此时就可以同步看到自己发送的消息。

那阳春白雪如何接受到消息呢,由于msg是共享,故也会通过v-html同步更新。

实现的效果如下:

VUE探索第四篇-VUEX-LMLPHP

四、Mutation、Action

   由上可知,组件访问state的状态变量,需要通过this.$store.state.xxx,如果state中的变量和层级较多,特别是一些操作要依赖多种状态的运算,使用会极不方便;另外,我们更希望这些状态变量不要直接暴露给组件,而提供一些方法和接口供组件调用,这样更安全,更加解耦。而Mutation就能解决这些问题,我们来看下。

在store实例中,添加mutations模块

const store = new Vuex.Store({
  state:{
	msg:"",
  },
  mutations:{
  	    //回调函数中,传入两个参加,state,payload
        send_msg(state,payload){
            state.msg+="<div class='showmsg'><img src='/static/mail.jpg'></img> "+payload.sendMessage+"</div>"
        }
    }
})

mutations添加一个回调方法send_msg,这个方法实现的核心功能与上面的send_msg类似,它有两个入参,state表示当前的状态树,payload为自定义的负荷对象,这里传入输入的文本消息sendMessage。

该方法并不是直接调用,而是采用commit提交来触发。

methods:{
   send_msg:function(){
    if(this.sendMessage==""){
        return
    }
    //采用commit方式提交
    this.$store.commit('send_msg', {sendMessage: this.sendMessage});
    this.sendMessage="";

   }
  }

注意:mutations的回调中的操作只能是同步的。

Action与Mutation类似,多个state使用mutation进行操作维护,那么多个mutation就用Action进行操作维护。action中可以使用异步调用方法。

我们继续增加actions模块。

const store = new Vuex.Store({
  state:{
	msg:"",
  },
  mutations:{
  	    //回调函数中,传入两个参加,state,payload
        send_msg(state,payload){
            state.msg+="<div class='showmsg'><img src='/static/mail.jpg'></img> "+payload.sendMessage+"</div>"
        }
    },
  actions:{
   	send_msg(context,payload){
   		context.commit('send_msg',payload);
   	}
   }
})

action是通过dispatch进行分发,

  methods:{
   send_msg:function(){
    if(this.sendMessage==""){
        return
    }
    //采用commit方式提交
    //this.$store.commit('send_msg', {sendMessage: this.sendMessage});
    this.$store.dispatch('send_msg', {sendMessage: this.sendMessage});
    this.sendMessage="";
   }
  }

五、Module

   在大型vue应用中,如果只有一个store文件保存状态变量,会使文件变得臃肿,也不利于协同开发的,module支持模块化开发,根据业务逻辑将store模块化各个对象,在store中组装起来,其格式如下:

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

上面以实现了基本的聊天过程,但是和待实现的样式还是有差别的,我们需要区别别人的记录和自己的记录,并在显示的位置和背景色上有所标识。我们分隔针对两者对象成两个module,每个对象维护自己的对话记录。如下:

VUE探索第四篇-VUEX-LMLPHP

新建一个store文件夹,分别创建三个子文件,分别是dialoga.js,dialogb.js,index.js。

在dailoga.js中我们创建"下里巴人"对应的状态数模块。

export default {
	namespaced: true,
	state:{
		msg:''
	},
    mutations:{
  	    //回调函数中,传入两个参加,state,payload
        send_msg(state,payload){
            state.msg+="<div class='showmsgright'><div>"+payload.sendMessage+"</div><img src='/static/mail.jpg'></img>	</div>"
        },
        rev_msg(state,payload){
        	state.msg+="<div class='showmsgleft'><img src='/static/femail.jpg'></img><div> "+payload.sendMessage+"</div></div>"
        }
    },
    actions:{
   	   send_msg(context,payload){

   		context.commit('send_msg',payload);
   	   },
   	   rev_msg(context,payload){
   		context.commit('rev_msg',payload);
   	   },
   }
}

此模块中的msg将存储"下里巴人"的聊天记录。namespace表示该模块启用命名空间,send_msg处理自己对话框的展示,rev_msg发送消息给对方,并在对方对话框展示。dialogb.js与该文件类似。

在index.js中引入两个模块文件,并导出store实例对象。

import Vue from 'vue'
import Vuex from 'vuex'
import dialoga from './dialoga.js'
import dialogb from './dialogb.js'
Vue.use(Vuex);

export default new Vuex.Store({
	modules:{
		dialoga,
		dialogb
	}
})

在man.js中,引入store,并注册。

import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import Vuex from 'vuex'
Vue.use(Vuex);

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,//注册store
  components: { App },
  template: '<App/>'
})

修改对话框组件中的发送方法。

methods:{
   send_msg:function(){
    if(this.sendMessage==""){
        return
    }
    //自有对话框展示消息
    this.$store.dispatch('dialoga/send_msg', {sendMessage: this.sendMessage});
    //发送给对方,并展示
    this.$store.dispatch('dialogb/rev_msg', {sendMessage: this.sendMessage});
    this.sendMessage="";
   }
  }

注意,此时的回调方法是带有路径的,表示调用哪个模块的方法,此种写法需要在配合module的命名空间使用。

六、Getters

Getter可以理解为store中的计算属性,它可以对state的状态数据进行筛选和重新计算,提供给组件使用,比如说我们要统计每个人发送消息的次数,在state中增加count属性。

state:{
		msg:'',
		count:0
	}

同时增加Getters模块

getters:{
       count:state=>{
       	return state.count++;
       }
   }

在组件中通过this.$store.getters.count调用。

七、辅助函数:mapState、mapGetters、mapActions、mapMutations

一般情况下,我们在组件中使用$store.state.xxx访问状态属性,比较繁杂,vuex提供了相关的辅助方法简化写法。我们来改写下

在组件页面中引入mapstate

import {mapState} from 'vuex';

创建mapSate

computed:{
    ...mapState({
      msg: state =>state.dialoga.msg
      })
  }

在模板中直接使用该变量

<div class="dialog" v-html="msg">

其他的几个辅助函数类似,注意,mapActions与mapMutations写在method中,mapSate与mapGetters要写在computed中。

10-04 14:31