Vue-6 Vuex(state, getters, mutations, actions, modules)
博主wx: -GuanEr-
,加博主进前端交流群
在项目研发过程中,组件和组件之间的结构关系非常复杂,到处存在数据交互。 props
,$emit
,路由传参是远远不足以支撑复杂的数据交互。
Vue
单独提供了 vuex
来做项目的状态管理,vuex
提供了公共数据的存储和操作方式,让组件之间的数据交互变得更简洁。
vuex
的核心理念就是存储一些供各个组件使用的数据,无论组件的嵌套结构如何,都能访问 vuex
中的数据。任何一个组件对其中的数据做了修改,在其他组件中的渲染会立即响应。
一、Vuex的安装和基本配置
和 vue-router
一样,要在项目中使用 vuex
,需要先下载依赖包。
npm i vuex --save
安装成功之后在 src
文件夹中创建项目目录:
src
└── App.vue
└── main.js
└── assets
└── components
// ...
└── router
// ...
└── store // 状态管理库的目录
index.js // 状态管理库的根配置文件
vuex
的基本配置:
// store>index.js
import Vue from 'vue'
import Vuex from 'vuex' // 引入 vuex
Vue.use(Vuex);
// 创建一个状态管理库实例并且导出,以备使用
export default new Vuex.Store({
// code
});
配置好了之后要将这个 Vuex.Store
实例对象传入根组件的实例化中,整个项目才能使用 vuex
:
// main.js
import Vue from 'vue'
import App from './App'
// 导入 store (省略)
import store from './vuex'
Vue.comfig.productionTip = false;
new Vue({
el: '#app',
// 将 store 传入根组件实例化的参数中
store,
components: { App },
template: '<App/>'
});
vuex
为 Store
类提供了五个配置,包含了可能出现的各种操作。
二、state
state
中存放的是 store
中所有的数据列表。
任何组件无论嵌套关系多复杂,都可以访问 state
中的数据。
// store>index.js 中的 state
state: {
num: 100,
bol: true
}
在任意组件中获取:
<!-- Any.vue -->
<template>
<div class="any">
<h2>num: {{ num }}</h2>
<h2>bol: {{ bol }}</h2>
</div>
</template>
<script>
export default {
name: 'Any',
computed: {
// 建议将对于 store 中的数据的获取或者操作写在计算属性中,这样当 store 中的数据变化时,也能及时响应
num() {
return this.$store.state.num;
},
bol() {
return this.$store.state.bol;
}
}
}
</script>
在其他任何组件中,对 state
中的数据做操作时,当前组件也会随之响应。
store
的好处之一就是无论组件和组件之间的嵌套结构是怎样的,都能共同访问 store
中的数据。
但是如果只是想访问 state
中的数据,且访问了很多个数据,那么为每一个数据单独的写一个计算属性,就显得很不理智。
vuex
提供 mapState
来批量的访问 state
中的数据
<!-- Any.vue -->
<script>
import { mapState } from 'vuex';
export default {
computed: {
...mapState([
'num',
'bol'
]);
}
}
</script>
调用方式不变。
三、getters
给一个示例,state
中有一个数组,数组中有各种类型的数据,有两个组件(A和B),都只想要这个数组中的 number
列表。
state: {
arr: [1, 4, 5, 'hello', true, 'world', 98]
}
我们可以在 A 和 B 的计算属性中先拿到 state
中的 arr
,对其做处理,返回数字列表:
<!-- A.vue -->
<script>
export default {
name: 'A',
computed: {
numArr() {
const arr = this.$store.state.arr;
return arr.filter(function(item) {
if(typeof item == 'number') return item;
});
}
}
}
</script>
如果 B 组件的需求和 A 组件一样,那么 numArr
中的这段代码,在 B 组件中,又要重新写一遍。
这样就形成了代码冗余。
所以 vuex
提供了 getters
,来根据代码需求过滤 state
中的数据,向外抛出过滤之后的数据。
// store>index.js 中导出的实例
export default new Vux.Store({
state: {
arr: [1, 4, 5, 'hello', true, 'world', 98]
},
getters: {
// getters 中的函数接收的第一个参数就是 state,可以通过它直接访问数据列表
numArr: state => {
const arr = state.arr;
return arr.filter(function(item) {
if(typeof item == 'number') return item;
});
}
}
});
现在 A 、B 组件想要访问 state
中的 arr
中的数字列表,可以直接访问 getters
。
<!-- A.vue -->
<script>
export default {
name: 'A',
computed: {
// 求 state 中的 arr 中的所有数字的和
numSum() {
const arr = this.$store.getters.numArr;
// 求和代码
}
}
}
</script>
<!-- B.vue -->
<script>
export default {
name: 'B',
computed: {
// 求 state 中的 arr 中的所有数字的最大值和最小值
numSum() {
const arr = this.$store.getters.numArr;
const min = Math.min(...arr);
const max = Math.max(...arr);
}
}
}
</script>
可以认为 getters
是 store
的计算属性,当 state
中的数据变化的时候,getters
返回的数据也会随之变化,且只有当它的依赖值发生了变化之后才会变化。
如果需求再变化:A 组件希望拿到 arr
中所有的数字,B 组件希望拿到 arr
中所有的字符串。那可能需要向 getters
中传递想要的数据类型,实现按需获取数据的效果。
所以我们也可以以函数的形式访问 getters
,并且向其传参。
// store 中的 getters
getters: {
getArrByType(state) {
return function(type) {
const arr = state.arr;
return arr.filter(function(item) {
if(typeof item == type) return item;
});
}
}
/*
关于以函数的形式访问getters官网给出的是箭头函数简写形式(两种意义一样):
getArrByType: (state) => (type) => {
const arr = state.arr;
return arr.filter(function(item) {
if(typeof item == type) return item;
});
}
*/
}
访问:
// 任意组件的 computed
computed: {
getData() {
const numArr = this.$store.getters.getArrByType('number');
const strArr = this.$store.getters.getArrByType('string');
}
}
同样的,如果 getters
只是获取而不传参,也不用为其写很多个计算属性。
vuex
提供 mapGetters
来批量获取 getters
。
// 任意组件的 js
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'getData1',
'getData2',
// ...
])
}
}
调用时直接将这些数组中的参数作为变量名去使用即可。
如果觉得 store
中对于 getters
的命名太冗长,也可以在某个组件中为其单独命名
// 在当前组件中,gd 就代表 getArrByType
mapGetters({
gd: 'getArrByType'
})
四、mutations
根据代码的需求,有时候我们可能也需要对 vuex
中的数据做一些更改。
vuex
规定,只能在其提供的 mutations
中修改 state
中的数据,否则严格模式下会直接报错。
// store>index.js 中 store 实例的导出
export default new Vuex.Store({
state: {
arr: [1,4,6]
},
mutations: {
// 向 arr 中添加数据
arrPush(state) {
// mutations 中接收的函数的第一个参数也是 state
state.arr.push(Math.floor(Math.random() * 100));
}
}
});
在任意组件中调用 mutations
中的函数需要使用到 store
的 commit
。
// 任意组件的 created
created() {
// 调用 store 的 mutations 中的 arrPush
this.$store.commit('arrPush');
}
也可以向 mutations
中的函数传参
mutations: {
arrPush(state, data) {
// data 就是在调用 mutations 时传递过来的参数
state.arr.push(data);
}
}
调用:
// 任意组件的 created
created() {
this.$store.commit('arrPush', Math.random());
}
vuex
建议: 向 mutations
中传递的参数尽量是一个对象,这样便于传输大量数据。
需要注意的是:mutations 中不能存在异步操作。
我们也可以使用 mapMutations
将组件中的 methods
映射成 mutations
中的函数。
methods: {
...mapMutations([
// 在当前组件中,ap 就代表 arrPush
ap: 'arrPush'
])
}
五、actions
vuex
不允许 mutations
中出现异步操作,那么有个异步操作刚好需要对 state
做改变,就需要写在 vuex
提供的 actions
中。
所以应该是在 actions
中写异步代码,在异步的某个过程中,如果需要对 state
做操作,就调用 mutations
中的函数。
// store>index.js 中的导出
export default new Vuex.Store({
state: '500 miles',
mutations: {
setStr(state, newStr) {
state.str = newStr;
}
},
actions: {
_setStr(context) {
// Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象
setTimeout(() => {
context.commit('setStr', '500 miles away from home');
});
}
}
});
调用:
// 任意组件的 created
created() {
this.$store.dispatch('setStr');
}
actions
也可以接收 dispatch
时被传递的数据:
actions: {
// 可以通过解构的方式从 context 中解构到 commit
_setStr({commit}, newStr) {
setTimeout(() => {
// newStr 就是调用 actions 时传递的参数
context.commit('setStr', newStr);
});
}
}
调用:
// 任意组件的 created
created() {
this.$store.dispatch('setStr', '500 miles away from home');
}
六、modules
如果项目对于 state
的依赖很强,使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。
将不同的需求的状态单独的声明成一个变量,然后将其导入到根对象的 modules
中,就能生效。
// store>index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const moduleA = {
state: {
msg: 'data of a'
},
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: {
msg: 'data of b'
},
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
state: {
msg: 'data of root'
}
modules: {
a: moduleA,
b: moduleB
}
});
// 创建一个状态管理库实例并且导出,以备使用
export default store
对于不同模块的 state
中的数据的访问:
// 任意组件的 created
created() {
const data1 = this.$store.state.msg;
const data2 = this.$store.state.a.msg;
const data3 = this.$store.state.b.msg;
}
除了state之外,getters、mutations、actions的访问方式不变。
如果要在子模块的 getters
中访问根 state
中的数据:
// 任意子模块的 getters
getters: {
getData(state, getters, rootState) {
// state: 当前模块的 state
// rootState: 根模块的 state
}
}
如果要在子模块的 actions
中访问:
// 子模块的actions
actions: {
fn(context) {
// context.state: 当前模块的 state
// context.rootState: 根模块的 state
}
}