(注:以下所有截图均来自我的微信公众号“生锈的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就实现了跨页面数据传递。随着项目的扩大,这一效果将会变得十分方便,且容易维护!