在 Vue 的学习中,Vuex 是一个专为 Vue 开发的应用程序的状态管理模式,当我们构建一个中大型的单页面应用程序时,Vuex可以更好的帮助我们在组件外部统一管理状态,Vuex具体使用方法查看文档笔记;
Vuex官方文档学习笔记
学习链接:Vuex官方文档
1. 安装
- 在一个模块化的打包系统中,先下载
vuex
第三方模块,再显式地通过Vue.use()
来安装Vuex
:
1 | npm install vuex --save |
1 | import Vue from 'vue' |
1.1 Vuex 是什么?
Vuex
是一个专为 Vue.js
应用程序开发的状态管理模式;状态管理模式介绍如下:
- 状态自管理应用包含以下几个部分:
state
:驱动应用的数据源;view
:以声明方式将state
映射到视图;actions
:响应在view
上的用户输入导致的状态变化;
Vuex
的基本思想是把组件的共享状态抽取出来,以一个全局单例模式管理,在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为;另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且易维护;Vuex
是专门为Vue.js
设计的状态管理库,以利用Vue.js
的细粒度数据响应机制来进行高效的状态更新;
1.2 开始
每一个 Vuex
应用的核心就是 store
(仓库),它包含着你的应用中大部分的状态 state
;
Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的;当 Vue 组件从
store
中读取状态的时候,若store
中的状态发生变化,那么相应的组件也会相应地得到高效更新; - 不能直接改变
store
中的状态;改变store
中的状态的唯一途径就是显式地 提交 (commit)mutation
,而非直接改变store.state.count
;这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用,代码如下:
1 | // 创建一个 store,提供一个初始 state 对象和一些 mutation |
2. 核心概念
2.1 State
2.1.1 单一状态树
Vuex
使用单一状态树——用一个对象就包含了全部的应用层级状态;每个应用将仅仅包含一个 store
实例;单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
2.1.2 在 Vue 组件中获得 Vuex 状态
Vuex
的状态存储是响应式的,从 store
实例中读取状态最简单的方法就是在计算属性中返回某个状态,如代码中每当 store.state.count
变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM:
1 | // 创建一个 Counter 组件 |
Vuex
通过 store
选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)
),通过在根实例中注册 store
选项,该 store
实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store
访问到,更新下 Counter
的实现:
1 | // 在根实例 app.vue 中进行注册 store选项; |
2.1.3 mapState 辅助函数
当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余,可以使用 mapState
辅助函数帮助我们生成计算属性;
1 | // 在单独构建的版本中辅助函数为 Vuex.mapState |
当映射的计算属性的名称与 state
的子节点名称相同时,我们也可以给 mapState
传一个字符串数组:
1 | computed: mapState([ |
2.1.4 对象展开运算符
mapState
函数返回的是一个对象;我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed
属性:
1 | computed: { |
2.1.5 组件仍然保有局部状态
使用 Vuex
并不意味着你需要将所有的状态放入 Vuex
;如果有些状态严格属于单个组件,最好还是作为组件的局部状态;你应该根据你的应用开发需要进行权衡和确定。
2.2 Getter
2.2.1 从 store 中的 state 中派生出一些状态
例如:对列表进行数据过滤并计算个数,代码如下:
1 | // 过滤函数获取todo.done 为true的值 |
Vuex
允许我们在 store
中定义“getter
”(可以认为是 store
的计算属性);就像计算属性一样, getter
的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算, Getter
接受 state
作为其第一个参数:
1 | const store = new Vuex.Store({ |
2.2.2 通过属性访问
Getter
会暴露为 store.getters
对象,你可以以属性的形式访问这些值:
1 | store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }] |
注意: getter
在通过属性访问时是作为 Vue
的响应式系统的一部分缓存其中的。
2.2.3 通过方法访问
还可以通过让 getter
返回一个函数,来实现给 getter
传参;在你对 store
里的数组进行查询时非常有用。
1 | getters: { |
注意: getter
在通过方法访问时,每次都会去进行调用,而不会缓存结果。
2.2.4 mapGetters 辅助函数
mapGetters
辅助函数仅仅是将 store
中的 getter
映射到局部计算属性:
1 | import { mapGetters } from 'vuex' |
如果想将一个 getter
属性另取一个名字,要使用对象形式:
1 | mapGetters({ |
2.3 Mutation
2.3.1 修改 store 中的状态
更改 Vuex
的 store
中的状态的唯一方法是提交 mutation
;每个 mutation
都有一个字符串的 事件类型 (type)
和 一个 回调函数 (handler)
;这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state
作为第一个参数,如下:
1 | const store = new Vuex.Store({ |
要调用此 mutation handler
,需要以相应的 type
调用 store.commit
方法:store.commit('increment')
2.3.2 提交载荷(Payload)——额外参数
可以向 store.commit
传入额外的参数,即 mutation
的 载荷(payload),例:
1 | // ... |
载荷还可以是一个对象,这样可以包含多个字段并且记录的 mutation
会更易读;
1 | // ... |
2.3.3 对象风格的提交方式
提交 mutation
的另一种方式是直接使用包含 type
属性的对象,使用对象风格的提交方式,整个对象都作为载荷传给 mutation
函数,因此 handler
保持不变:
1 | // 定义在store中的函数 |
2.3.4 Mutation 需遵守 Vue 的响应规则
- 最好提前在你的
store
中初始化好所有所需属性。 - 当需要在对象上添加新属性时,你应该使用
Vue.set(obj, 'newProp', 123)
, 或者以新对象替换老对象
;例如:state.obj = { ...state.obj, newProp: 123 }
2.3.5 使用常量替代 Mutation 事件类型
使用常量替代 mutation
事件类型在各种 Flux
实现中是很常见的模式;
1 | // mutation-types.js |
2.3.6 Mutation 必须是同步函数(重要原则)
参考下面的例子:
1 | mutations: { |
2.3.7 在组件中提交 Mutation
可以在组件中使用 this.$store.commit('xxx')
提交 mutation
,或者使用 mapMutations
辅助函数将组件中的 methods
映射为 store.commit
调用(需要在根节点注入 store
):
1 | import { mapMutations } from 'vuex' |
2.3.8 Vuex 中 mutation
都是同步事务
在 mutation
中混合异步调用会导致你的程序很难调试。例如,当你调用了两个包含异步回调的 mutation
来改变状态,你怎么知道什么时候回调和哪个先回调呢?这就是为什么我们要区分这两个概念,为了处理异步操作,来看一看 Action
的功能;
1 | store.commit('increment') |
2.4 Action
Action
类似于 mutation
,不同在于:
- Action 提交的是
mutation
,而不是直接变更状态; - Action 可以包含任意异步操作;
Action
函数接受一个与 store
实例具有相同方法和属性的 context
对象;因此你可以调用 context.commit
提交一个 mutation
,或者通过 context.state
和 context.getters
来获取 state
和 getters
;
注册一个简单的 action
例子:
1 | const store = new Vuex.Store({ |
2.4.1 分发 Action
Action
通过 store.dispatch
方法触发: store.dispatch('increment')
1 | // Action 还可以在 action 内部执行异步操作(区别于mutation 必须同步执行): |
购物车示例,涉及到调用异步 API
和分发多重 mutation
:
1 | actions: { |
2.4.2 在组件中分发 Action
在组件中使用 this.$store.dispatch('xxx')
分发 action
,或者使用 mapActions
辅助函数将组件的 methods
映射为 store.dispatch
调用(需要先在根节点注入 store
):
1 | import { mapActions } from 'vuex' |
2.4.3 组合 Action
Action
通常是异步的,那么如何知道 action
什么时候结束呢?首先要明白 store.dispatch
可以处理被触发的 action
的处理函数返回的 Promise
,并且 store.dispatch
仍旧返回 Promise
:
1 | actions: { |
一个 store.dispatch
在不同模块中可以触发多个 action
函数;在这种情况下,只有当所有触发函数完成后,返回的 Promise
才会执行;
2.5 Module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时, store
对象就有可能变得相当臃肿;为了解决以上问题, Vuex
允许我们将 store
分割成模块( module
),每个模块拥有自己的 state; mutation; action; getter; 嵌套子模块
;
1 | const moduleA = { |
2.5.1 模块的局部状态
对于模块内部的 mutation
和 getter
,接收的第一个参数是模块的局部状态对象;
1 | const moduleA = { |
同样,对于模块内部的 action
,局部状态通过 context.state
暴露出来,根节点状态则为 context.rootState
:
1 | const moduleA = { |
对于模块内部的 getter
,根节点状态会作为第三个参数暴露出来:
1 | const moduleA = { |
2.5.2 命名空间
默认情况下,模块内部的 action、mutation 和 getter
是注册在全局命名空间的——这样使得多个模块能够对同一 mutation
或 action
作出响应;
若希望模块具有更高的封装度和复用性,可以通过添加 namespaced: true
的方式使其成为带命名空间的模块;当模块被注册后,它的所有 getter; action; mutation
都会自动根据模块注册的路径调整命名。例如:
1 | const store = new Vuex.Store({ |
2.5.3 模块动态注册
使用 store.registerModule
方法注册模块,注册后就可以通过 store.state.myModule
和 store.state.nested.myModule
访问模块的状态:
1 | // 注册模块 `myModule` |
2.5.4 模块重用
有时需要创建一个模块的多个实例,如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 store
或模块间数据互相污染的问题;实际上这和 Vue
组件内的 data
是同样的问题:解决办法也是相同的——使用一个函数来声明模块状态(仅 2.3.0+ 支持):
1 | const MyReusableModule = { |
3. 项目结构
规定了一些需要遵守的规则:
- 应用层级的状态应该集中到单个
store
对象中; - 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的;
- 异步逻辑都应该封装到 action 里面;
下面是项目结构示例:
1 | ├── index.html |
4. (未)插件
5. 严格模式
开启严格模式,仅需在创建 store
的时候传入 strict: true
:
1 | const store = new Vuex.Store({ |
在严格模式下,无论何时发生了状态变更且不是由 mutation
函数引起的,将会抛出错误;这能保证所有的状态变更都能被调试工具跟踪到;
5.1 开发环境与发布环境
不要在发布环境下启用严格模式 严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失,处理如下:
1 | const store = new Vuex.Store({ |
6. 表单处理
当在严格模式中使用 Vuex
时,在属于 Vuex
的 state
上使用 v-model
会比较棘手:<input v-model="obj.message">
;用“Vuex 的思维”去解决这个问题的方法是:给 <input>
中绑定 value
,然后侦听 input
或者 change
事件,在事件回调中调用 action
:
1 | <input :value="message" @input="updateMessage"> |
1 | // ... |
1 | // 下面是 mutation 函数: |
6.1 双向绑定的计算属性
使用带有 setter
的双向绑定计算属性:
1 | <input v-model="message"> |
1 | // ... |