一、Vuex概述
官方解释:
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
设想每个Vue组件都只需要关心自己的数据和数据处理逻辑,组件之间完全独立,没有共享数据的需求,那么web应用会非常简单。
但是实际情况是,一个web应用中不同组件常常会有数据共享的需求。例如文档类型的应用,编辑的内容和大纲之间要共享数据,大纲列表组件需要根据编辑的内容生成文档大纲、电商类型的应用,商品展示列表需要根据用户选择的分类展示相应的商品,那么商品列表组件需要知道分类选项组件中用户选择的是哪个类别。
我们知道Vue父组件可以和子组件之间通过props、事件、ref引用来进行通信,但是这种组件之间的通信方式在规模较大的应用中会有些力不从心。因此我们更倾向于将不同组件通用的数据提取出来统一管理。
我们知道Vue组件根据数据渲染视图,不同的数据对应不同的视图。我们也可以理解为web应用处于不同的“状态”,每个状态对应数据的一组取值。因此我们将数据抽出来统一管理也可以称为“状态管理”。
总之,Vuex就是专为Vue.js开发的状态管理工具,用于解决组件之间数据共享的需求。
状态管理工具都需要包含哪些要素?
-
初始状态
-
改变状态
-
监听状态的改变
Vuex都有以下这些API:
-
state
-
getters
-
mutations
-
actions
-
module
-
辅助函数:mapState、mapGetters、mapMutations、mapActions
-
createStore
其中state和getters用来保存状态;mutations和actions用来改变状态;监听状态用的是Vue组件中的computed属性(所以vuex的使用都是在computed中声明);module是用来组织整个应用的状态管理,使状态划分模块,更易于管理;辅助函数用来在监听状态时候简化代码,createStore则用来创建状态管理对象。
Vuex状态管理就是创建一个对象(store),这个对象上面保存了应用中大部分数据,组件可以通过store获取数据,也可以改变数据的值。上面说的这些能力一个普通的js对象也可以做到。store和普通的对象的区别是:
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用
Vuex的数据流是组件中触发Action,Action提交Mutations,Mutations修改State。 组件根据 States或Getters来渲染页面。
二、Vuex基本使用
基本使用:
使用Vuex中的createStore方法创建一个store,注意到创建store的对象中目前有state和mutations两个属性。state中保存着我们需要使用的数据 count,mutations里面是改变state中数据的方法,方法接受一个state参数,通过改变这个state参数的属性(count)的值就可以修改状态了。
执行了store.commit('increase');之后,Vuex会执行mutations中的increase方法,让count加一。
import {createStore} from 'vuex';
const store = createStore({
state: {
count: 0
},
mutations: {
increase(state) {
state.count++;
}
}
});
store.commit('increase');
console.log(store.state.count); // 1
mutations还可以接收参数,方法.(state,参数):参数放置在形参的第二个位置
import {createStore} from 'vuex';
const store = createStore({
state: {
count: 0
},
mutations: {
increase(state, {count}) {
state.count = count;
}
}
});
store.commit('increase', {count: 2});
console.log(store.state.count); // 2
getters
getters和state的关系类似于Vue组件data属性和computed属性的关系,getters根据state或者其他getters计算出另一个变量的值,当其依赖的数据变化时候,它也会实时更新。
import {createStore} from 'vuex';
const store = createStore({
state: {
count: 0
},
getters: {
msg(state) {
return state.count % 2 === 0 ? '偶数': '奇数';
}
},
mutations: {
increase(state) {
state.count++;
}
}
});
store.commit('increase');
console.log(store.state.count, store.getters.msg); // 1 "奇数"
actions
既然已经有了mutations可以改变state,为什么还需要actions呢?因为mutations不应该用于异步修改状态。实际上mutations是可以异步修改状态的,比如:
mutations: {
increase(state) {
setTimeout(() => {
state.count++;
}, 1000);
}
}
但是这样做的话,Vuex是无法知道修改state.count的时机的,因为它是在异步回调里面指定的,因此Vuex无法在调试工具中打印出我们实际改变state的操作。
因此Vuex中有actions API,在actions中可以进行异步操作,在actions中可以提交mutations,也可以触发其他的action。
import {createStore} from 'vuex';
const store = createStore({
state: {
count: 0
},
mutations: {
increase(state) {
state.count++;
}
},
actions: {
increase(context) {
setTimeout(() => {
// contex.dispatch可以用于触发其他action
context.commit('increase');
}, 1000);
}
}
});
store.dispatch('increase');
console.log(store.state.count); // 0
setTimeout(() => {
console.log(store.state.count); // 1
}, 1000);
三、Vuex modules模块
通常在一个web应用中,会有很多数据,都放在一个store里面会让数据很混乱,因此我们应该根据功能模块将数据划分成一个一个的模块。
Vuex支持将store划分成模块,并且模块还可以嵌套。
多个模块的示例:
在调用createStore时候传入的对象中,提供modules属性,传入两个子模块。
import {createStore} from 'vuex';
const moduleA = {
state: {
name: 'a'
}
};
const moduleB = {
state: {
name: 'b'
}
};
const store = createStore({
modules: {
a: moduleA,
b: moduleB
}
});
console.log(store.state.a.name); // a
console.log(store.state.b.name); // b
包含嵌套子模块时候,访问子模块的getters和state的示例:
(注意默认情况下,所有子模块的getters、mutations和actions都是注册在全局的)
import {createStore} from 'vuex';
const store = createStore({
state: {
counter: 0
},
getters: {
counter10times(state) {
return state.counter * 10;
}
},
modules: {
a: {
state: {aName: 'A·a'},
getters: {
aFirstName(state) {
return state.aName.split('·')[0];
}
},
modules: {
c: {
state: {cName: 'C·c'},
getters: {
cFirstName(state) {
return state.cName.split('·')[0];
}
}
}
}
},
b: {
state: {bName: 'B·b'},
getters: {
bFirstName(state) {
return state.bName.split('·')[0];
},
bNewName(state, getters, rootState, rootGetters) {
// 访问局部state
const {bName} = state;
// 访问全局state
const {a: {c: {cName}}} = rootState;
// 访问局部getters
const {bFirstName} = getters;
// 访问全局getters
const {cFirstName} = rootGetters;
return `${bName} ${bFirstName} ${cName} ${cFirstName}`;
}
}
}
}
});
// 子模块的state通过子模块路径访问
console.log(store.state.a.c.cName);
// 子模块的getters都注册到了全局,在store.getters下面直接能访问到
console.log(store.getters.bNewName);
一个多模块,commit mutation和dispatch action的示例:
模块可以提交其他模块的mutation,从而改变其他的模块的状态;模块也可以触发其他模块的action
import {createStore} from 'vuex';
const store = createStore({
state: {
counter: 0
},
mutations: {
increaseCounter(state) {
state.counter++;
}
},
modules: {
a: {
state: {aName: 'A·a'},
mutations: {
changeAName(state) {
state.aName = 'A-a';
}
},
actions: {
callChangeCNameAsync({dispatch}) {
// 触发其他模块的action
setTimeout(() => {
dispatch('changeCNameAsync');
}, 500);
}
},
modules: {
c: {
state: {cName: 'C·c'},
mutations: {
changeCName(state, payload) {
state.cName = `C-c-${payload.suffix}`;
}
},
actions: {
changeCNameAsync({commit, rootState}) {
setTimeout(() => {
// 提交其他模块的mutation,mutation是全局的
commit('increaseCounter');
// 提交局部模块的mutation
commit('changeCName', {
suffix: rootState.counter
});
}, 500);
}
}
},
}
},
b: {
state: {bName: 'B·b'},
mutations: {
changeBName(state) {
state.bName = 'B-b';
}
}
}
}
});
// 全局的commit
store.commit('increaseCounter');
console.log(store.state.counter); // 1
// 子模块mutation注册到全局了
store.commit('changeCName', {suffix: '123'});
console.log(store.state.a.c.cName); // C-c-123
// 子模块commit其他模块的mutation
store.dispatch('changeCNameAsync');
setTimeout(() => {
console.log(store.state.a.c.cName); // C-c-2
}, 1000);
// 子模块dispatch其它模块的action
store.dispatch('callChangeCNameAsync');
setTimeout(() => {
console.log(store.state.a.c.cName); // C-c-3
}, 1500);
默认情况下,所有模块的getters、mutations和actions都是注册到全局的,这样如果多个子模块的getters、mutations和actions中有同名时候,会导致覆盖,引起问题。因此通常我们需要给子模块加命名空间。
给子模块加命名空间的方式是给子模块加namespaced属性并赋值为true。
加了命名空间后,访问state的方式不变(因为默认state也不是注册到全局的),访问getters时候需要加命名空间前缀,如果访问模块自身子模块的getters、提交mutations、触发actions时候,只需要加相对路径前缀,不需要加自身命名空间前缀,例如模块a访问其子模块c时候,不需要加'a/c'前缀,只需要'c'就可以了。
import {createStore} from 'vuex';
const store = createStore({
state: {
counter: 0
},
getters: {
counter10times(state) {
return state.counter * 10;
}
},
modules: {
a: {
namespaced: true,
state: {aName: 'A·a'},
getters: {
aFirstName(state) {
return state.aName.split('·')[0];
}
},
modules: {
c: {
namespaced: true,
state: {cName: 'C·c'},
getters: {
cFirstName(state) {
return state.cName.split('·')[0];
}
}
}
}
},
b: {
namespaced: true,
state: {bName: 'B·b'},
getters: {
bNewName(state, getters, rootState, rootGetters) {
// 局部state
const bName = state.bName.split('·')[0];
// 其他模块的getter
const cFirstName = rootGetters['a/c/cFirstName'];
// 其他模块的state
const aName = rootState.a.aName;
return `${bName} ${cFirstName} ${aName}`;
}
}
}
}
});
// getters命名空间
console.log(store.getters['b/bNewName']); // B C A·a
// 子节点state仍然是通过节点路径访问
console.log(store.state.a.c.cName); // C·c
在使用了命名空间的多模块的提交mutations和触发actions:
import {createStore} from 'vuex';
const store = createStore({
state: {
counter: 0
},
mutations: {
increaseCounter(state) {
state.counter++;
}
},
modules: {
a: {
namespaced: true,
state: {aName: 'A·a'},
mutations: {
changeAName(state) {
state.aName = 'A-a';
}
},
actions: {
callChangeCNameAsync({dispatch}) {
// 触发子模块的action,是相对于自身的路径,不需要加a前缀
setTimeout(() => {
dispatch('c/changeCNameAsync');
}, 500);
}
},
modules: {
c: {
namespaced: true,
state: {cName: 'C·c'},
mutations: {
changeCName(state, payload) {
state.cName = `C-c-${payload.suffix}`;
}
},
actions: {
changeCNameAsync({commit, rootState}) {
setTimeout(() => {
// 提交其他模块的mutation,mutation是全局的
commit('increaseCounter', null, {root: true});
// 提交局部模块的mutation,不需要加前缀
commit('changeCName', {
suffix: rootState.counter
});
}, 500);
}
}
},
}
},
b: {
namespaced: true,
state: {bName: 'B·b'},
mutations: {
changeBName(state) {
state.bName = 'B-b';
}
}
}
}
});
// 全局的commit
// 注意加了命名空间之后,提交根模块的mutation和触发根模块的action时候,都需要加上{root: true}的选项
store.commit('increaseCounter', null, {root: true});
console.log(store.state.counter); // 1
// 子模块mutation注册到全局了
store.commit('a/c/changeCName', {suffix: '123'});
console.log(store.state.a.c.cName); // C-c-123
// 子模块commit其他模块的mutation
store.dispatch('a/c/changeCNameAsync');
setTimeout(() => {
console.log(store.state.a.c.cName); // C-c-2
}, 1000);
// 子模块dispatch其它模块的action
store.dispatch('a/callChangeCNameAsync');
setTimeout(() => {
console.log(store.state.a.c.cName); // C-c-3
}, 1500);