文章目录
Vuex 是什么?
什么是“状态管理模式”?
State单一状态树
在 Vue 组件中获得 Vuex 状态
mapState 辅助函数
对象展开运算符
组件仍然保有局部状态
Getter
通过属性访问
通过方法访问
mapGetters 辅助函数
Mutation
提交载荷(Payload)
对象风格的提交方式
使用常量替代 Mutation 事件类型
Mutation 必须是同步函数
在组件中提交 Mutation
Action
Module
Vuex 是什么?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
什么是“状态管理模式”?
Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是:
Flux 架构就像眼镜:您自会知道什么时候需要它。
State单一状态树
Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
在 Vue 组件中获得 Vuex 状态
Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:
// Vue 的原型上面添加一个属性,$store=> 指向仓库部分
// this.$store 访问到仓库数据以及方法
Vue.use(Vuex);
export default new Vuex.Store({
// Vuex 五大模块
// vue=> data 定义页面之间共享数据 => 响应式的 => 定义方式和vue data是一样的
// state 本身是一个JS对象,页面刷新的时候,如果你没有做持久化处理,数据重新清空
state: {
count: 666,
},
// vue 中的计算属性
getters: {
total: function (state) {
return state.count * 100;
},
},
mutations: {},
actions: {},
modules: {},
});
<!-- 测试页面 -->
<template>
<div class="">
<button @click="count++">+1</button>
<h1>{{ count }}</h1>
<h2>{{ $store.state.count }}</h2>
<h3>{{ $store.getters.total }}</h3>
</div>
</template>
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
27
28
29
30
31
mapState 辅助函数
当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
1
2
3
4
对象展开运算符
mapState 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。但是自从有了对象展开运算符,我们可以极大地简化写法:
computed: {
localComputed () { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
1
2
3
4
5
6
7
组件仍然保有局部状态
使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。
Getter
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。
getters: {
total: function (state) {
return state.count * 100;
},
},
1
2
3
4
5
通过属性访问
Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值:
<h3>{{ $store.getters.total }}</h3>
1
Getter 也可以接受其他 getter 作为第二个参数:
getters: {
// ...
doneTodosCount (state, getters) {
return getters.doneTodos.length
}
}
store.getters.doneTodosCount // -> 1
1
2
3
4
5
6
7
8
通过方法访问
你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: fa
1
2
3
4
5
6
7
8
9
mapGetters 辅助函数
mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
const INCREMENT_BY = 'INCREMENT_BY';
mutations: {
// increment: function (state) {},
// 同步提交
increment(state, payload) {
// console.log(payload);
// state.count += payload;
// console.log(payload);
// state.count += payload.amount;
console.log(payload);
state.count += payload;
},
[INCREMENT_BY](state, payload) {
state.count += payload;
},
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
你不能直接调用一个 mutation 处理函数。这个选项更像是事件注册:“当触发一个类型为 increment 的 mutation 时,调用此函数。”要唤醒一个 mutation 处理函数,你需要以相应的 type 调用 store.commit 方法:
methods: {
...mapMutations(["increment", "INCREMENT_BY"]),
handleClick: function () {
// this.increment(10);
this.INCREMENT_BY(10);
},
},
1
2
3
4
5
6
7
提交载荷(Payload)
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
1
2
3
4
5
6
7
8
对象风格的提交方式
store.commit({
type: 'increment',
amount: 10
})
1
2
3
4
当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此处理函数保持不变
使用常量替代 Mutation 事件类型
使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然
Mutation 必须是同步函数
一条重要的原则就是要记住 mutation 必须是同步函数
在组件中提交 Mutation
你可以在组件中使用 this.$store.commit(‘xxx’) 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。
methods: {
...mapMutations(["increment", "INCREMENT_BY"]), // this.increment => this.$store.commit
handleClick: function () {
// this.increment(10);
this.INCREMENT_BY(10);
},
},
1
2
3
4
5
6
7
Action
Action 类似于 mutation,不同在于:
●Action 提交的是 mutation,而不是直接变更状态。
●Action 可以包含任意异步操作。
actions: {
// 获取用户的个人信息的
getUserInfo: function (store) {
//发送请求
myAxios.get('user').then((res) => {
// console.log(res);
// res.data
store.commit(SAVE_USER, res.data);
});
},
},
1
2
3
4
5
6
7
8
9
10
11
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了。
Module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
import myAxios from '@/api/instance';
const SAVE_USER = 'SAVE_USER';
const login = {
namespaced: true,
state: () => ({
user: {},
}),
getters: {},
mutations: {
[SAVE_USER](state, payload) {
state.user = payload;
},
},
actions: {
// 获取用户的个人信息的
getUserInfo: function (store) {
myAxios.get('user').then((res) => {
// console.log(res);
// res.data
store.commit(SAVE_USER, res.data);
});
},
},
};
export default login;
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
const INCREMENT_BY = 'INCREMENT_BY';
const test = {
namespaced: true,
state: () => ({
count: 666,
}),
getters: {
total: function (state) {
return state.count * 100;
},
},
mutations: {
// increment: function (state) {},
// 同步提交
increment(state, payload) {
// console.log(payload);
// state.count += payload;
// console.log(payload);
// state.count += payload.amount;
console.log(payload);
state.count += payload;
},
// ['INCREMENT_BY'](state, payload) {
// state.count += payload;
// },
[INCREMENT_BY](state, payload) {
state.count += payload;
},
},
actions: {},
};
export default test;