(注:以下所有截图均来自我的微信公众号“生锈的TRUENO”,欢迎各位关注~)
Vuex是一个专为Vue.js应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
每个Vue组件都由三个基本的部分组成:state,view,actions:
其中,state是驱动应用的数据源,view以声明的方式将state映射到视图,actions用来响应在view上的用户输入导致的状态变化。具体到一个简单的Vue组件中,state可以指组件的script中的data,view可以指组件的template中对data里数据的绑定和使用,actions则可以指组件的template中对于组件的script中方法的绑定。
简单地说,上述Vue组件中的三个部分完成了从数据到视图的渲染,再到用户操作视图从而修改数据这一“单向数据流”。组件的状态随着数据流的不断运动而不断改变。
这么看来,貌似单个Vue组件就可以完成状态的管理,那为什么还需要Vuex呢?我们来举个栗子:
一个购物车应用,当用户将想要的物品添加到购物车后,需要点击付款,从而跳转到另外一个页面,而这个付款页面除了填写地址等信息外,肯定还要有用户刚刚选中的商品信息,也就是购物车中的数据。这个简单的需求涉及到了一个问题:数据的跨页面传递。
我们可以想一下一般来说如何实现数据的跨页面传递。1.可以把当前页面需要传递的数据放到url里,在页面跳转的同时,就把数据传递到新的页面了;2.还可以把数据放到cookie里,利用浏览器缓存,让新的页面调用cookie中的数据,从而实现数据的传递;3.利用数据库,在一个页面中修改数据后,先将数据存到数据库,然后在另一个页面中,从数据库中对该数据进行读取。总而言之,上述的方法都需要一个媒介,把数据传来传去,这会增加系统的不可维护性。
因此,Vuex为我们提供了一个统一管理多个组件共享的数据的方式,我们只需要对一组共享数据进行维护,然后共享这些数据的所有组件都可以对这些数据进行使用和修改,这就大大提升了系统的可维护性。
下面,举一个简单的栗子来说明Vuex的使用方法和效果:
需求:写一个简单的购物车功能,一共两个页面,一个是购物车页面,用来确定商品的数目,另一个是计数页面,用来显示当前购物车中的商品总数。最终要实现在购物车页面修改后的商品数目,可以和计数页面显示的商品总数一致的效果。
先看一下应用的目录结构:
是我用vue-cli搭建的一个项目,这个目录只显示了主要的部分,其中“components”中的两个组件分别是计数组件和购物车组件,router中的index.js用于设置前端路由,store是Vuex的核心代码,main.js是项目的入口文件。
为了使用Vuex,需要在入口文件main.js中引入,main.js代码如下:
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'//引入vuex
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,//引入vuex
components: { App },
template: '<App/>'
})
store是Vuex的核心代码,首先来看一下state.js,该文件用来定义共享数据:
/*
状态对象
*/
export default{
totalNum:0//商品总数
}
state只是实现了共享数据的定义,它可以让Vue组件使用共享数据,但是如果组件需要修改共享数据,那就要用到mutation.js:
/*
直接更新state的多个方法的对象
*/
export default{
changeNum(state,currentNum){
state.totalNum = currentNum
}
}
在Vuex中,mutation是唯一可以修改共享数据的入口,即任何对共享数据的修改操作都要通过mutation。
我们可以看到在store中,还有一个getters.js和actions.js,其中getters用来从state中的原始数据进一步派生出新的数据,因为有时往往组件需要的是经过进一步处理的派生数据,而action则用来处理异步请求,因为mutation只能用来处理同步请求,但是有些数据,比如商品列表,是通过调用后端接口获得的,因此是异步的,只有数据获得到才可以修改state中的值,所以,Vuex使用action处理异步请求,action并不直接处理state中的数据,而是在回调函数中,提交mutation,这样就能保证先获得数据,再修改state。而在本次示例中, 并没有用到getter和action,因此暂且不展示这两个的使用。
最后,store需要一个index.js将上述文件组织起来:
/*
vuex最核心的管理对象
*/
import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
import mutations from './mutations';
import actions from './actions';
import getters from './getters';
Vue.use(Vuex)
export default new Vuex.Store({
state,
mutations,
actions,
getters,
})
我们再来看看两个Vue组件,首先是计数组件count.vue:
<template>
<div>
<div>当前购物车中总的商品数为:{{totalNum}}</div>
<router-link to="/">返回购物车界面</router-link>
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: 'count',
data() {
return {
}
},
computed:{
...mapState(['totalNum'])
},
}
</script>
<style scoped>
</style>
我们看到该组件需要用到共享数据“totalNum”,因此,需要先在script中引入mapState,然后在computed中使用mapState并选中totalNum,这样,totalNum就和组件中的data一样,可以直接使用了。
我们再来看看购物车组件shoppingCar.vue:
<template>
<div class="hello">
<div>
{{msg}}
</div>
<br>
<br>
<span>
<input type="text" :value="totalNum"/>
<button @click="increaseNum">+</button>
<button @click="decreaseNum">-</button>
</span>
<router-link to="/count">查看当前总商品数</router-link>
</div>
</template>
<script>
import {mapState,mapMutations} from 'vuex'
export default {
name: 'shopping',
data () {
return {
msg: '欢迎来到购物车界面',
}
},
computed:{
...mapState(['totalNum'])
},
methods:{
...mapMutations(['changeNum']),
increaseNum(){
this.changeNum(this.totalNum+1);
},
decreaseNum(){
this.changeNum(this.totalNum-1);
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
可以发现,该组件同样需要用到“totalNum”这个共享数据,与此同时,该组件还需要对这个共享数据进行修改,而修改共享数据,则还需要引入“mapMutations”,并在script的methods中,使用mapMutations选中mutation中的方法“changeNum”,此后,这个方法就和vue组件中其他的method一样了,可以在视图中直接用@click进行绑定。
从整体来看Vuex的工作机制如下图所示:
好,我们来看一下运行效果,首先在购物车页面修改商品数:
然后,点击“查看当前商品总数”,进入计数界面:
可以发现:数目和购物车页面中修改的结果一致,并且URL并没有传递参数,代码中也没有使用cookie和数据库,只需要正确使用Vuex就实现了跨页面数据传递。随着项目的扩大,这一效果将会变得十分方便,且容易维护!