一、什么是Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
这个状态自管理应用包含以下几个部分:
- state,驱动应用的数据源;
- view,以声明方式将state映射到视图;
- actions,响应在view上的用户输入导致的状态变化。
以下是一个表示“单向数据流”理念的极简示意:

更复杂的情况
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。

二、什么情况下我应该使用 Vuex?
虽然 Vuex 可以帮助我们管理共享状态,但也附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的
global event bus
就足够您所需了。但是,如果您需要构建是一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是:
Flux 架构就像眼镜:您自会知道什么时候需要它。
三、安装
首先需要创建一个基于Vue的工程,通常你可以这样做:
1 2 3 4 5 6 7 8
| # 全局安装 vue-cli $ npm install --global vue-cli # 创建一个基于 webpack 模板的新项目 $ vue init webpack my-project # 安装依赖,走你 $ cd my-project $ npm install $ npm run dev
|
- 利用npm包管理工具,进行安装 vuex。在控制命令行中输入下边的命令就可以了。
要注意的是这里一定要加上 –save,因为你这个包我们在生产环境中是要使用的。
通过简单的依赖安装操作,vuex就算引用成功了,接下来我们就可以尽情的玩耍了。
一个简单的示例,在main.js文件中输入如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import Vue from 'vue' import Vuex from 'vuex'
Vue.use(Vuex) const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } } }) store.commit('increment')
new Vue({ el: '#app', template: `<div>{{ count }}</div>`, computed: { count () { return store.state.count } } })
|
运行后可以看到页面上显示一个1,即函数increment已经正确的执行,count由初始的0自加后变为1输出,
需要指出的是,我们通过提交 mutation 的方式,而非直接改变 store.state.count,是因为我们想要更明确地追踪到状态的变化。
通常我们不会这样去使用它,更好的使用方法是:
- 新建一个store文件夹(这个不是必须的),并在文件夹下新建store.js文件,文件中引入我们的vue和vuex。
1 2
| import Vue from 'vue'; import Vuex from 'vuex';
|
- 在store.js文件中,用Vue.use对vuex进行引用。
4.在main.js 中引入新建的vuex文件
1
| import store from './store/store'
|
- Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex),通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import Vue from 'vue' import App from './App' import router from './router' import store from './store/store'
Vue.config.productionTip = false
new Vue({ el: '#app', router, store, template: '<App/>', components: { App } })
|
四、使用
- 现在我们store.js文件里增加一个常量对象。
- 用export default 封装代码,让外部可以引用。
1 2 3 4 5 6
| export default new Vuex.Store({ name: 'store', state: { count: 1 } })
|
- 新建一个Vue的测试模板,位置在components文件夹下,名字随便起,我这里叫count.vue。在模板中我们使用输出count 的值(注意:由于前面已经将store注入到vue的实例中,故不需要再次引用)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <template> <div> <h2>{{msg}}</h2> <hr/> <h3>{{$store.state.count}}</h3> </div> </template> <script> export default { data () { return { msg: 'Hello Vuex' } }, computed: { count () { return this.$store.state.count } } } </script>
|
- 在store.js文件中加入两个改变state的方法。
1 2 3 4 5 6 7 8
| mutations: { increment (state) { state.count += 1 }, decrement (state) { state.count -= 1 } }
|
- 在test.vue模板中加入两个按钮,并调用mutations中的方法。
1 2 3 4
| <div> <button @click="$store.commit('increment')">+</button> <button @click="$store.commit('decrement')">-</button> </div>
|
- 在router文件夹下的index.js中引用文件,定义对应的路由,运行程序并进入该界面,点击按钮查看效果
1 2 3 4 5
| { path: '/count', name: 'Count', component: Count }
|
五、state访问状态对象
state,这个就是我们说的访问状态对象,它就是我们SPA(单页应用程序)中的共享值。
访问状态对象赋值给内部对象,也就是把stroe.js中的值,赋值给我们模板里data中的值。有三种赋值方式
- 通过computed的计算属性直接赋值
computed属性可以在输出前,对data中的值进行改变,我们就利用这种特性把store.js中的state值赋值给我们模板中的data值。
1 2 3 4 5
| computed:{ count(){ return this.$store.state.count; } }
|
这里需要注意的是return this.$store.state.count这一句,一定要写this,要不你会找不到$store的。这种写法很好理解,但是写起来是比较麻烦的,那我们来看看第二种写法。
- 通过mapState的对象来赋值
我们首先要用import引入mapState。
1
| import {mapState} from 'vuex'
|
然后还在computed计算属性里写如下代码:(根据需要选用一种方法即可)
1 2 3 4 5 6 7 8 9 10 11 12
| computed: mapState({ count: state => state.count,
countAlias: 'count',
countPlusLocalState (state) { return state.count + this.localCount } })
|
- 当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
1 2 3 4
| computed: mapState([ 'count' ])
|
- 还可以采用简化写法(个人比较喜欢这种写法本质上和第三种是一个意思,后面的mutations中也有类似的写法)
1 2 3 4
| ...mapState({ num: 'count' })
|
这个算是最简单的写法了,在实际项目开发当中也经常这样使用。
六、Mutations
我们已经定义并且拿到了state的值,那么如何去改变他们的状态呢?
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数
- 修改状态
Vuex提供了commit方法来修改状态,前边我们在给按钮添加点击事件时已经使用过该方法了,代码如下。
1 2
| <button @click="$store.commit('increment')">+</button> <button @click="$store.commit('decrement')">-</button>
|
store.js文件:
1 2 3 4 5 6 7 8
| mutations: { increment (state) { state.count += 1 }, decrement (state) { state.count -= 1 } }
|
传值:这只是一个最简单的修改状态的操作,在实际项目中我们常常需要在修改状态时传值。比如上边的例子,是我们每次只加1,而现在我们要通过所传的值进行相加。其实我们只需要在Mutations里再加上一个参数,这个参数又称为mutation 的 载荷(payload),并在commit的时候传递就就可以了。我们来看具体代码:
现在store.js文件里给increment 方法加上一个参数n。
1 2 3 4 5 6 7 8
| mutations: { increment (state, n) { state.count += n }, decrement (state) { state.count -= 1 } }
|
在count.vue里修改按钮的commit( )方法传递的参数,我们传递10,意思就是每次加10。
1 2 3 4
| <p> <button @click="$store.commit('increment ',10)">+</button> <button @click="$store.commit('decrement ')">-</button> </p>
|
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:
1 2 3 4 5 6 7 8
| mutations: { increment (state, payload) { state.count += payload.amount } } store.commit('increment', { amount: 10 })
|
更推荐的写法:对象风格的提交方式
提交 mutation 的另一种方式是直接使用包含 type 属性的对象:
1 2 3 4
| store.commit({ type: 'increment', amount: 10 })
|
- 上面的写法还是复杂了点,有没有更简单的呢,答案是肯定的,模板获取Mutations方法
实际开发中我们也不喜欢看到$store.commit( )这样的方法出现,我们希望跟调用模板里的方法一样调用。
例如:@click=”reduce” 就和没引用vuex插件一样。要达到这种写法,只需要简单的两部就可以了:
1)、在模板count.vue里用import 引入我们的mapMutations:
1
| import { mapState,mapMutations } from 'vuex';
|
2)、在模板的<\script>标签里添加methods属性,并加入mapMutations
1 2 3
| methods:mapMutations([ 'increment ' ]),
|
通过上边两部,我们已经可以在模板中直接使用我们的reduce或者add方法了,就像下面这样。
1
| <button @click="increment({amount: 10})">-</button>
|
同state一样,该写法也可以利用对象展开运算符进行简化
1 2 3
| ...mapMutations({ add: 'increment' })
|
调用
1
| <button @click="add({amount: 10})">-</button>
|
七、getters计算过滤操作
getters从表面是获得的意思,可以把他看作在获取数据之前进行的一种再编辑,相当于对数据的一个过滤和加工。你可以把它看作store.js的计算属性。
- getters基本用法:
比如我们现在要对store.js文件中的count进行一个计算属性的操作,就是在它输出前,给它加上100.我们首先要在store.js里用const声明我们的getters属性。1 2 3 4 5
| getters: { count: state => { return (state.count += 100) } }
|
在store.js里的配置算是完成了,我们需要到模板页对computed进行配置。在vue 的构造器里边只能有一个computed属性,如果你写多个,只有最后一个computed属性可用,所以要对上节课写的computed属性进行一个改造。
1 2 3 4 5 6
| ...mapState({ name: 'name', count () { return this.$store.getters.count } })
|
使用
需要注意的是,你写了这个配置后,在每次count 的值发生变化的时候,都会进行加100的操作。
- 用mapGetters简化模板写法
我们都知道state和mutations都有map的引用方法把我们模板中的编码进行简化,我们的getters也是有的,我们来看一下代码。
首先用import引入我们的mapGetters
1
| import { mapState, mapMutations, mapGetters } from 'vuex'
|
在computed属性中加入mapGetters
1 2 3
| ...mapGetters({ counted: 'count' })
|
八、actions异步修改状态
actions和之前讲的Mutations功能基本一样,不同点是,actions是异步的改变state状态,而Mutations是同步改变状态。
- 在store.js中声明actions
actions是可以调用Mutations里的方法的,我们还是继续在上节课的代码基础上进行学习,在actions里调用increment和decrement两个方法。
1 2 3 4 5 6 7 8 9 10
| actions: { incrementAsync ({ commit }, payload) { setTimeout(() => { commit('increment', payload) }, 1000) }, decrementAction (context) { context.commit('decrement') } }
|
在actions里写了两个方法incrementAction 和decrementAction ,在方法体里,我们都用commit调用了Mutations里边的方法。细心的小伙伴会发现这两个方法传递的参数也不一样。
- ontext:上下文对象,这里你可以理解成store本身。
- {commit}:直接把commit对象传递过来,可以让方法体逻辑和代码更清晰明了。
- 模板中的使用
我们需要在count.vue模板中编写代码,让actions生效。我们先复制两个以前有的按钮,并改成我们的actions里的方法名,分别是:incrementAction 和decrementAction 。
1 2
| <button @click="incrementAction({amount: 10})">+action</button> <button @click="$store.commit('decrementAction')">-action</button>
|
改造一下我们的methods方法,首先还是用扩展运算符把mapMutations和mapActions加入。
1 2 3 4 5 6
| methods:{ ...mapActions({ incrementAsync: 'incrementAsync', decrementAction: 'decrementAction' }) },
|
你还要记得用import把我们的mapActions引入才可以使用。
九、module模块组
随着项目的复杂性增加,我们共享的状态越来越多,这时候我们就需要把我们状态的各种操作进行一个分组,分组后再进行按组编写。那今天我们就学习一下module:状态管理器的模块组操作。
- 声明模块组:
在vuex/store.js中声明模块组,我们还是用我们的const常量的方法声明模块组。代码如下:
1 2 3
| const moduleA={ state,mutations,getters,actions }
|
声明好后,我们需要修改原来 Vuex.Stroe里的值:
1 2 3
| export default new Vuex.Store({ modules:{a:moduleA} })
|
- 在模板中使用
现在我们要在模板中使用count状态,要用插值的形式写入。
1
| <h3>{{$store.state.a.count}}</h3>
|
如果想用简单的方法引入,还是要在我们的计算属性中rutrun我们的状态。写法如下:
1 2 3 4 5
| computed:{ count(){ return this.$store.state.a.count; } },
|