一、什么是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);
我们模拟微信的聊天过程,如下:
三、State
State是vuex的单一状态树,保存各组件共享的数据源,它将会包含在store实例中。
我们来分析下这个聊天界面,"下里巴人"和"阳春白雪"分别创建两个聊天界面组件DialogA和DialogB,两者的对话保存到变量msg中,而变量msg作为共享数据源由state维护。
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同步更新。
实现的效果如下:
四、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,每个对象维护自己的对话记录。如下:
新建一个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中。