Vuex 是专门为 vue.js 应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
基本概念
每一个 Vuex 应用的核心就是store
(仓库),它是一个容器,包含着应用中大部分的 state(状态)。其有 2 点 不同于单纯的全局对象:
- store 中的 state 响应式的,当 Vue 组件从 store 中读取 state (状态)的时候,若 store 中 state(状态) 发生变化,相应的组件也会得到更新
- 不能直接改变 store 中的 state(状态)。唯一途径就是*显示地 commit (提交) mutation
1 | import Vue from "vue"; |
State
Vuex 使用单一状态树,用一个对象包含全部的应用层级状态。每个应用仅包含一个 store 实例。
在 Vue 组件中获取 Vuex 状态
由于 Vuex 的状态存储是响应式,从 store 实例中读取状态最简单方法:在就算属性中返回某个状态
Vuex 通过store
选项,可将 store 实例 从根组件”注入”到每一个子组件中。之后子组件就能通过this.$store
访问到 store 实例。
1 | import Vue from "vue"; |
mapState 辅助函数
当一个组件需要获取多个状态时,将这些状态都声明为计算属性会重复冗余。mapState
辅助函数可帮助创建计算属性,并返回 store 中的状态。
1 | // 在单独构建的版本中辅助函数为 Vuex.mapState |
Getter
Vuex
允许在 store 中 定义 “getter”,它可理解为 store 的计算属性。getter
的返回值会根据它的依赖 被缓存起来,且只用当它的依赖发生了改变才会被重新计算。
在 store 上注册 getter,getter 方法 接受以下参数:
state
, 如果在模块中定义则为模块的局部状态getters
, 等同于store.getters
rootState
, 全局 state 状态,即store.state
rootGetters
,全局getter
, 即 所有 getters
访问 getter
注册的 getter,会暴露 为
store.getters
对象,可 通过属性形式 访问1
2
3
4
5computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}让 getter 返回一个函数,来 实现给 getter 传参,则可通过方法访问,且每次都会取进行调用,而不会缓存结果
1
2
3
4
5
6
7
8
9getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find((todo) => todo.id === id);
};
}
// 组件内 访问
this.$store.getters.getTodoById(2); // -> { id: 2, text: '...', done: false }
mapGetters
辅助函数
该mapGetters
辅助函数将 store 中的 getter 映射到局部计算属性
1 | import { mapGetters } from "vuex"; |
Mutation
更改 Vuex 的 store 中的状态的唯一方法就是提交 mutation。
mutation 非常类似于事件: 事件类型 (type)+ 处理函数(handler)。这个处理函数就是实际进行状态 更改的地方 。
mutation 必须时同步函数,因为要追踪 状态的改变,每一条 mutation 都会被记录,如果 mutation 是异步函数,devtools
不知道什么时候回调函数被调用的。
注册 Mutation
在 store 上注册 mutation, 处理函数 总是接受 2 个参数
state
,如果 定义在模块中,则为模块的局部状态payload
,提交的载荷
1 | const store = new Vuex.Store({ |
组件内提交 Mutation
- 载荷方式:
this.$store.commit('increment',{amount: 10})
- 对象方式:
this.$store.commit({type:'increment', amount: 10})
Action
当需要处理异步操作,需要在 Action。它类似 与 Mutation,不同在于:
- Action 提交 的是 mutation, 而不是直接改变状态
- Action 可以包含任意 异步操作
注册 Action
在 store 上 注册 action,处理函数接受 2 个参数,
context
,一个与 store 实例具有相同方法和属性的对象,其包含以下属性:state
, 等同于store.state
, 若在 模块中 则为局部状态rootState
, 全局的state
commit
, 等同于store.commit
dispatch
, 等同于store.dispatch
getters
, 等同于store.getters
rootGetters
, 全局的 getter
payload
,载荷
1 | const store = new Vuex.Store({ |
组件内分发 Action
- 载荷方式:
this.$store.dispatch('incrementAsync',{amount: 10})
- 对象方式:
this.$store.dispatch({type:'incrementAsync', amount: 10})
mapActions
辅助函数
mapActions
辅助函数将组件的 methods 映射为 store.dispatch
调用
1 | import { mapActions } from "vuex"; |
组合 Action
Action 通常是异步的,如何知道 action 什么时候结束?
因为store.dispatch
是可以处理被触发的 action 的处理函数 返回 的 Promise,并且store.dispatch
仍旧返回 Promise。那么利用此,就可以组合多个 action,处理复杂的异步流程。
需要注意
一个
store.dispatch
在不同模块中可以触发多个 action 函数。只用当所有触发函数完成后,返回的 Promise 才会执行
注册 action,处理函数 返回 Promise
1 | actions: { |
在分发 action 时,就可处理 返回的 Promise
1 | store.dispatch("actionA").then( |
在另一个 action 中也可以
1 | actions: { |
使用 async/await
语法,轻松组合 action
1 | // 假设 getData() 和 getOtherData() 返回的是 Promise |
Module
由于使用单一的状态树,应用的 store 对象可能变得十分臃肿。为解决该问题,Vuex 可以将 store 分割成 小的模块(module)。
每个模块都拥有自己的 state、getters、mutation、action、嵌套的子模块。
命名空间
默认情况下,模块内部的 action 、mutation、getter 都是注册在全局命名空间的,这使得 多个 模块能 对同一 mutation 、 action作出响应。
通过添加 namespaced:true
,使其成名 带命名空间的模块。当有命名空间的模块被注册后,它所有 getter、action、mutation 都会自动根据模块注册的路径 调整命名。
1 | const store = new Vuex.Store({ |
在带命名空间的模块内访问全局内容
想 使用全局 state、getter,
rootState
和rootGetters
作为第三、第四个参数出入 getter 的处理函数,也会在context
对象的属性中想 在全局命名空间分发 action、提交 mutation,将
{root: true}
作为第三个参数,传入dispatch
,commit
即可
1 | modules: { |
在带命名空间的 模块注册全局 action
添加 root: true
,并将这个 action 的定义放在函数 handler
中
1 | { |
带命名空间的 辅助函数
1 | computed: { |
对于上种情况,可将模块的命名 空间名称字符串 作为第一个参数传递给上述函数
1 | computed: { |
表单处理
在严格模式下,状态的变更不是由 mutation 处理函数引起的,将会抛出错误。因此,在 state 上使用v-model
会比较棘手,例如
1 | <input v-model="obj.message" /> |
当用户输入时,v-model
会试图直接修改ob.message
,而非提交 mutation. 有 2 种解决方法
不使用
v-model
指令,给<input>
中绑定 value,然后监听input
或change
事件,事件回调中提交 mutation.1
2
3
4
5
6
7
8
9
10
11
12<input :value="message" @input="updateMessage">
computed: {
...mapState({
message: state => state.obj.message
})
},
methods: {
updateMessage (e) {
this.$store.commit('updateMessage', e.target.value)
}
}使用带有
setter
的 双向绑定的计算属性,1
2
3
4
5
6
7
8
9
10
11
12
13<input v-model="message">
// ....
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set () {
this.$store.commit('updateMessage', value)
}
}
}