什么是Vuex
Vue.js官网中是这样描述的:
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
总结一下就是:Vuex是Vue中的状态管理工具
。
为什么要使用Vuex
Vuex使工程的各种状态的可控性得以提高。
比如:
parent.vue组件里面有两个子组件:borther.vue和sister.vue,parent和brother或者和sister通信都简单,props,emit就好了,但是如果brother和sister需要通信的话怎么办呢?
如果不用Vuex的话可以想到,brother跟parent说,我想和妹妹说话,你帮我把这句话带给妹妹吧,妹妹想跟哥哥说话的时候也需要让parent给帮忙传递,很明显,这样是不太好控制的。
如果我们用了Vuex就会是这样的:
brother想跟sister说句话,提交到store里面,sister直接就可以监测到。
引入Vuex
CDN/直接下载
直接引入vue.js 和 vuex.js文件就可以
npm
npm install vuex --save
项目中新建store文件,创建一个store实例:
export default new Vuex.Store({
state: {
talkMsg: []
},
mutations: {
updateTalk (state,msg) {
state.talkMsg.push(msg);
}
}
});
组件中引入store,可以通过 this.$store.state.talkMsg
获取state中的数据,通过this.$store.commit('updateTalk',data)
向store中提交数据
Vuex的核心
vuex的核心概念有:state,getter,mutation,action,module。
state
state存放项目中所有的公共状态,相当于vue中的data,
在组件中访问state有两种方式:
- 在组件的计算属性中返回状态:
computed:{
talkMsg(){
return store.state.talkMsg;
},
},
这种方式是最简单的方式,但是这种方式在模块化的构建系统中需要state的组件要频繁导入,并且在测试组件是需要模拟状态。
- 在组件中引入通过
store
选项将状态注入组件中,调用方法:
export default {
name: 'parent',
store,
data () {
return {
brotherTalk:'',
}
},
computed:{
talkMsg(){
return this.$store.state.talkMsg;
},
},
};
getter
getter相当于store的计算属性,定义方式如下:
getters: {
talkMinLength3: state => {
return state.talkMsg.filter(msg => msg.msg.length>3);
}
},
在组件中可以随意使用,使用方式:
this.$store.getters.talkMinLength3
如果需要传参:
getters: {
talkMinLength3: state =>(length) => {
return state.talkMsg.filter(msg => msg.msg.length>length);
}
},
使用:
this.$store.getters.talkMinLength3(2)
mutation
- 相当于vue中的methods,用于更改store中的状态,state作为第一个参数传入,也可以传入额外参数,
- 不能在组件中直接调用,只能commit,
store.js
mutations: {
updateTalk (state,msg) {
state.talkMsg.push(msg);
}
}
brother.vue
this.$store.commit('updateTalk',parme);
- 也可以以对象的方式提交:
brother.vue
this.$store.commit('updateTalk',{
user:'brother',
msg:'hello world'
});
或者:
this.$store.commit({
type:'updateTalk',
user:'brother',
msg:'hello world'
});
store.js
mutations: {
updateTalk (state,payload) {
state.talkMsg.push(payload);
}
}
- Mutations必须是同步函数
Action
Action 类似于Mutation,不同的是,
- Mutation改变store中的状态,而Action提交的是Mutation
- Action 处理异步操作,Mutation必须是同步函数
Auction 接收一个和store实例具有相同方法的context对象,提交mutation的时候可以用context.commit()
提交,获取state和getter时可以用context.state
和context.getters
来获取。
组件中出发action用store.dispatch
触发,
store.js
mutations: {
updateTalk (state,payload) {
state.talkMsg.push(payload);
}
},
actions:{
updateTalk({commit},msg){
setTimeout(() => {
commit('updateTalk',msg)
}, 1000)
},
},
borther.vue
this.$store.dispatch('updateTalk',parme);
module
module就是模块,当项目比较大,状态比较多的时候就需要状态按照模块划分开来,每一个模块都可以有自己的state,getter,mutation,action,也可以嵌套子模块。
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
辅助函数
vuex的辅助函数有:mapState,mapGetters,mapMutations,mapAuctions,辅助函数大同小异,以mapGetters为例,看一下Vuex里面,mapGetters是如何实现的。
先看mapGetters是如何使用的:使用前需要先引入,
import { mapGetters } from 'vuex'
mapGetters可以通过传入数组或者对象的方式使用,其中...
并不是辅助函数提供的,而是es6中的展开运算符
- 这种方式是传入数组的情况:
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
- 这种方式是传入对象的情况:
mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
以上面传入对象的情况为例,解析之后会变成:
computed:{
doneCount(){
return this.$store.getters.doneTodosCount
}
}
这是vuex里面mapGetters的实现代码:
var mapGetters = normalizeNamespace(function (namespace, getters) {
var res = {};
normalizeMap(getters).forEach(function (ref) {
var key = ref.key;
var val = ref.val;
// The namespace has been mutated by normalizeNamespace
val = namespace + val;
res[key] = function mappedGetter () {
if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
return
}
if (!(val in this.$store.getters)) {
console.error(("[vuex] unknown getter: " + val));
return
}
return this.$store.getters[val]
};
// mark vuex getter for devtools
res[key].vuex = true;
});
return res
});
可以看到mapGetters主要用normalizeNamespace
和normalizeMap
方法实现的,我们先看一下normalizeMap
是怎么实现的:
function normalizeMap (map) {
return Array.isArray(map)
? map.map(function (key) { return ({ key: key, val: key }); })
: Object.keys(map).map(function (key) { return ({ key: key, val: map[key] }); })
}
可以看到normalizeMap
方法主要是用来格式化getters参数的,这次以上面数组传入方式为例,通过getters方法之后的格式为:
[{
key:'doneTodosCount',
val:'doneTodosCount',
},{
key:'anotherGetter',
val:'anotherGetter',
}]
再看一下normalizeNamespace
方法是怎么实现的:
function normalizeNamespace (fn) {
return function (namespace, map) {
if (typeof namespace !== 'string') {
map = namespace;
namespace = '';
} else if (namespace.charAt(namespace.length - 1) !== '/') {
namespace += '/';
}
return fn(namespace, map)
}
}
可以看出normalizeNamespace
方法主要是处理命名空间的,就先不分析了,所以绝部分逻辑都是通过normalizeMap
方法来处理的。
可以看出所谓的辅助函数,确实就是为了给使用者们带来便利的一种语法糖,大家不用把这个想的太复杂~