Vuex
官方定义:
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
说得直白点,vuex就是vue.js中管理数据状态的一个库,通过创建一个集中的数据存储,供程序中所有组件访问。
一个数据只要放在了vuex中,当前项目所有的组件都可以直接访问这个数据。
vuex有以下常用的几个核心属性概念:
- State
- Getter
- Mutation
- Action
- Module
具体用法我们稍后揭晓。
2、实际场景
普通的父传子和子传父,或是兄弟组件之间的互传值,都是两个组件之间的数据连接,但如果数据需要多组件共享,并且数据量庞大,那么就不适宜用中央事件总线来解决。此时,我们需要一个更加强大的,能够维护庞大数据的东西,它就是vuex,我们称之为:状态管理。
3、什么时候用vuex?
是不是学了就必须要在项目中使用呢?来看官网的回答:
如果你的项目比较简单,建议还是别强行使用vuex了。
另外,值得注意的点:vuex会随着页面刷新或关闭,将所有数据恢复至最初始的状态,所以它并不能替代localStorage。
4、安装vuex
如果你的项目是较为大型的,那么建议在创建项目时直接选择安装vuex,如果创建时并未选择安装,请参考:《官网vuex安装》。
5、State
vuex中的state类似于data,用于存放数据,只不过这个数据是所有组件公用的。
我们来实现一个计数功能,但咱们把数据存放到vuex中的state,这里是专门用来存放组件共享的数据的。来看一下怎么实现:
首先,在 store/index.js
中:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
num: 0 // 定义了一个num
}
})
复制代码
然后在组件中:
<template>
<div>
<h3>{{$store.state.num}}</h3>
</div>
</template>
复制代码
这样,我们就获取到了state中的数据。
但在html中写这么长一串,始终有点难以阅读,因此,可以在computed中获取这个值,再传入html中:
computed: {
num(){
return this.$store.state.num
}
}
// 标签中
<h3>{{num}}</h3>
复制代码
那么能否在data中存储这个数据呢?比如这样:
data() {
return {
num: this.$store.state.num
};
},
复制代码
答案是可以的,但不太建议。因为data中的数据可以修改,但通常唯一能够修改state的方式是通过vuex中的mutations,所以此处我们了解一下即可。
6、Getters
vuex中的getters类似于computed计算属性,getters的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
store/index.js
中:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
num: 2
},
getters: {
// 这里的参数state可以让我们快速获取到仓库中的数据
doubleNum(state) {
return state.num * 2;
}
}
})
复制代码
组件中:
<template>
<div>
<h3>{{num}}</h3>
</div>
</template>
<script>
export default {
computed: {
num(){
return this.$store.getters.doubleNum
}
}
};
</script>
复制代码
7、Mutations
官网指出:
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
OK!那我们来修改一下这个num:
store/index.js
中(这里暂时先把getters去掉,避免大家学习混乱):
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
num: 2
},
mutations: {
// payload专业名称为“载荷”,其实就是个参数
addNum(state, payload) {
state.num += payload;
}
}
})
复制代码
组件中:
<template>
<div>
<h3>{{num}}</h3>
<button @click="btnClick">累加2</button>
</div>
</template>
<script>
export default {
computed: {
num(){
return this.$store.state.num
}
},
methods: {
btnClick(){
// 使用commit来触发事件,第二个参数是要传递给payload的数据
this.$store.commit('addNum', 2)
}
}
};
</script>
复制代码
我们来亲自尝试一下:
mutations: {
addNum(state, payload) {
// 这里给mutations添加定时器,也相当于是异步操作
setTimeout(() => {
state.num += payload;
}, 1000)
}
},
复制代码
可以自己在浏览器查看相对于的效果。
8、Actions
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
num: 2
},
mutations: {
addNum(state, payload) {
state.num += payload;
}
},
actions: {
// context是一个对象,包含了commit和state
AsyncAddNum(context,payload) {
setTimeout(() => {
context.commit('addNum', payload)
}, 1000)
}
}
})
复制代码
组件中:
<template>
<div>
<h3>{{num}}</h3>
<button @click="btnClick">累加2</button>
</div>
</template>
<script>
export default {
computed: {
num(){
return this.$store.state.num
}
},
methods: {
btnClick(){
// dispatch是分发到意思,其实也是触发Actions中的方法
this.$store.dispatch('AsyncAddNum', 2)
}
}
};
</script>
复制代码
当然,上面actions中的写法有点累赘,我们还可以改写:
AsyncAddNum({ commit },payload) {
setTimeout(() => {
commit('addNum', payload)
}, 1000)
}
// 如果你还想获取state中的值,可以这样:
AsyncAddNum({ commit,state },payload) {
console.log(state.num); // 2
setTimeout(() => {
commit('addNum', payload)
}, 1000)
}
复制代码
那么,既然actions中可以拿到state的值,能否直接改这个值,而不触发mutations呢?答案是可以的,但…还是devtool的问题:
可以看到,devtool并未更改值,因此,我们还是需要借助mutations来实现值的修改。
9、辅助函数
获取单个数据或触发某个方法比较容易,我们直接拿到和触发就行,但如果要获取的数据和触发的方法很多个,我们就比较麻烦了,这时候我们需要借用辅助函数。比如,我们刚刚只写了累加,现在再补充一个递减。然后来看看辅助函数怎么用。
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({ state: { num: 2, title: '标题' }, getters: { doubleNum(state) { return state.num * 1; } }, mutations: { addNum(state, payload) { state.num += payload; }, cutNum(state, payload) { state.num -= payload; } }, actions: { AsyncAddNum({ commit },payload) { setTimeout(() => { commit('addNum', payload) }, 300) }, AsyncCutNum({ commit }, payload) { setTimeout(() => { commit('cutNum', payload) }, 300) } }})
复制代码
此时,要拿到num和title的话,我们可以在组件中:
<template> <div> <h2>{{title}}</h2> <h3>{{num}}</h3> </div></template><script>// 引入辅助函数mapStateimport {mapState} from 'vuex'export default { // 在computed中引用 computed: { ...mapState(['title', 'num']) }};</script>
复制代码
如果打算从getters中取出num:
<h3>{{doubleNum}}</h3><script>import {mapState, mapGetters} from 'vuex'export default { computed: { ...mapState(['title']), ...mapGetters(['doubleNum']) }};</script>
复制代码
如果打算把mutations和actions中的方法引入:
<template> <div> <h2>{{title}}</h2> <h3>{{doubleNum}}</h3> <button @click="addNum(2)">累加2</button> <button @click="AsyncCutNum(2)">递减2</button> </div></template><script>import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'methods: { // 这里负责引入,我们把引入后的事件直接写在标签上,顺便把参数也带上 ...mapMutations(['addNum']), ...mapActions(['AsyncCutNum'])}</script>
复制代码
如果你必须把点击事件写在methods中,而不是标签上的话,你也可以这样:
<template> <div> <button @click="btnClick1">累加2</button> <button @click="btnClick2">递减2</button> </div></template> <script>import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'export default { methods: { ...mapMutations(['addNum']), ...mapActions(['AsyncCutNum']), btnClick1(){ // 直接通过this.xxx来调用辅助函数引入的事件 this.addNum(2) }, btnClick2(){ this.AsyncCutNum(2) } }};</script>
复制代码
10、Module
假设我们把累加单独抽出来作为一个模块,在store下新建一个 add/index.js
文件:
export default { namespaced: true, // 命名空间,为true时,可以在store中把当前模块文件夹名称(add),当作模块名使用 state: { num: 2 }, getters: { doubleNum(state) { return state.num * 1; } }, mutations: { addNum(state, payload) { state.num += payload; } }, actions: { AsyncAddNum({ commit }, payload) { setTimeout(() => { commit('addNum', payload) }, 300) } }}
复制代码
把有关累加的所有内容,都移动至本文件。再到原来仓库index.js中的modules添加:
import add from './add'export default new Vuex.Store({ ..., modules: { add }})
复制代码
在组件中使用时,稍微有些变化:
...mapState({ 'title': 'title', 'num1': 'num1', 'num': state => state.add.num // module中state值的获取方法和getters等不太一样,需要写函数形式}),...mapGetters({ 'doubleNum': 'add/doubleNum'})...mapMutations({ 'addNum': 'add/addNum'})
复制代码
11、拆分写法
实际上我们可以把state、getter、mutation、action和module都抽离出来,这样可以让store文件看着更加简洁。我们来将 store/index.js
进行拆分:
state.js
:
export default {
num1: 0,
title: '标题'
}
复制代码
mutations.js
:
export default {
cutNum(state, payload) {
state.num1 -= payload;
}
}
复制代码
actions.js
:
export default {
AsyncCutNum({ commit }, payload) {
setTimeout(() => {
commit('cutNum', payload)
}, 300)
}
}
复制代码
modules.js
:
import add from './add'
export default {
add
}
复制代码
最后,在 store/index.js
中:
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import modules from './modules'
Vue.use(Vuex)
export default new Vuex.Store({
state,
mutations,
actions,
modules
})
复制代码
12、MutationTypes与ActionTypes
有时候,我们mutations和actions中,同个事件名要出现在好几份文件,如果一个出错,就很难检查是哪里的问题,所以我们可以把这些事件名归到一个js文件,由它来统一调配:
mutationTypes.js
中:
export const CUT_NUM = 'cutNum'
复制代码
mutations.js
中:
import { CUT_NUM } from '@/store/mutationTypes'
export default {
[CUT_NUM](state, payload) {
state.num1 -= payload;
}
}
复制代码
actionTypes.js
中:
export const ASYNC_CUT_NUM = 'AsyncCutNum';
复制代码
actions.js
中:
import { ASYNC_CUT_NUM } from '@/store/actionTypes'
export default {
[ASYNC_CUT_NUM]({ commit }, payload) {
setTimeout(() => {
commit('cutNum', payload)
}, 300)
}
}
复制代码
在组件中:
import { ASYNC_CUT_NUM } from '@/store/actionTypes'
import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'
...mapActions([ASYNC_CUT_NUM]),
复制代码