状态管理模式Vuex
状态管理模式Vuex
一、工作流程
组件(例如用户点击按钮)通过Dispatch
触发Actions
,Actions
通过Commit
触发Mutatios
,Mutations
改变State
,伴随着State
的改变重新渲染组件。
用户可以直接操作Mutatios吗?答案是可以的,Mutations支持同步操作,Actions支持异步操作。
二、State
在 Vue 组件中获得 Vuex 状态
1、通过store实例直接获取
由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:
1 | import store from '@/store/index' |
上面的方式在模块化开发中,需要在需要使用store
的组件中频繁地导入store
。
Vuex 通过 Vue 的插件系统将 store 实例从根组件中“注入”到所有的子组件里。且子组件能通过 this.$store
访问到。于是上面的方式可以改写为:
1 | // 获取store中定义的购物车商品数据 |
2、mapState 辅助函数获取state
当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState
辅助函数帮助我们生成计算属性,让你少按几次键:
1 | // 在单独构建的版本中辅助函数为 Vuex.mapState |
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState
传一个字符串数组。
1 | computed: mapState( |
3、mapState结合对象展开运算符
mapState
函数返回的是一个对象,通过使用对象展开运算符,我们就可以在使用mapState
的同时定义其它的计算属性值了。
1 | computed: { |
我们可以把mapState
的返回值打印出来看看:
1 | created () { |
三、Getter
为什么会有Getter
?有时候我们需要从store
中派生出一些状态,例如购物车总金额、或者对列表进行过滤计数等等。
如果在不同的.vue
中的多个组件需要使用到该状态,那我们就需要把Computed
中写的购物车总金额、列表过滤的计算属性完整的复制到每一个需要使用到该状态的.vue
中,或者抽取到一个公共的函数中对它进行引用–无论哪种方式都不是很理想。
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。我们可以在getter
中对状态进行处理返回。
可以类比Java中的get方法。
Getter 接受 state 作为其第一个参数(自动传入该参数):
我们可以通过state
参数获取到状态,对其进行处理再进行返回。
1 | const store = createStore({ |
1、通过属性访问(getter直接返回处理后的state)
Getter 会暴露为 store.getters
对象,你可以以属性的形式访问这些值。
例如:返回商品价格大于6000的商品。
1 | export default new Vuex.Store({ |
Getter 也可以接受其他 getter 作为第二个参数:也就是在当前getter中调用定义的其它的getter方法
1 | getters: { |
我们可以在任何组件中很容易的通过像使用state
一样使用它:
1 | computed: { |
注意,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。
2、通过方法访问(getter返回的是函数)
你也可以通过让 getter 返回一个函数,来实现给 getter 传参。
1 | getters: { |
在组件中进行使用:
1 | this.$store.getters.getGoodsById(1) |
1 | created () { |
注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。
3、mapGetters 辅助函数
mapGetters
辅助函数仅仅是将 store 中的 getter 映射到局部计算属性,与mapState
类似。
1 | import { mapGetters } from 'vuex' |
如果你想将一个 getter 属性另取一个名字,使用对象形式:
1 | // getter 属性另取一个名字,使用对象形式 |
调用:
1 | created () { |
四、Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。
4.1 不带载荷的mutation
例如:定义一个事件类型type
为changePrice
,回调函数handler
内容为state.cart.totalPrice = 100
的mutation
。
1 | export default new Vuex.Store({ |
你不能直接调用一个 mutation 处理函数。即如下:
1 | this.$store.mutations.changePrice() |
mutations
更像是事件注册:“当触发一个类型为 changePrice
的 mutation 时,调用此函数。”
要使用一个 mutation 处理函数,需要用相应的 type 调用 store.commit 方法:
1 | this.$store.commit('changePrice') |
4.2 带载荷的mutation
可以向 store.commit
传入额外的参数,以传入需要的数据,即 mutation 的载荷(payload):
1 | export default new Vuex.Store({ |
调用mutation
:
1 | this.$store.commit('changePriceA', 500) |
在大多数情况下,载荷通常是以对象形式传入的,这样可以包含多个字段并且记录(dev-tool工具会记录到,本文后面会讲到)的 mutation 会更易读:
1 | export default new Vuex.Store({ |
调用mutation
:
1 | this.$store.commit('changePriceB', { |
4.3 对象风格的提交方式
提交 mutation 的另一种方式是直接使用包含 type
属性的对象:
1 | this.$store.commit({ |
当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此处理函数保持不变
1 | mutations: { |
分别使用两种风格提交mutation
,查看打印出来的载荷.
1 | mutations: { |
1 |
|
结果:可以看到以对象风格提交的数据,载荷是包含type
的对象,所以接受数据时要注意使用解构或使用payload.
的方式获取到数据。
4.4 mapMutations
在组件中可以使用 this.$store.commit('xxx')
提交 mutation,或者使用 mapMutations
辅助函数将组件中的 methods 映射为 store.commit
调用(需要在根节点注入 store
)。
1 | import { mapMutations } from 'vuex' |
注意:mapMutations需要使用在methods内,而不是computed,因为mapMutations返回的是mutation事件函数,说到这那为什么mapGetters返回的函数可以在computed中定义呢?那我们直接把东西打印出来看看。
1 | craeted(){ |
如果将mapMutations
定义在computed内:console.log(this.changePriceB)
的输出值为undefined
。
4.5 Mutation 必须是同步函数
Mutation的回调函数handler中的内容必须是同步的,否则其中异步改变的状态将不会被devtool 中的 mutation 日志记录到。
例如:下面定义的testMutation
中包含一个axios异步请求与setTimeout定时器,其中将state.count的值进行了加1,这两个加1的操作将不会被devtool的日志记录器锁记录到。
1 | mutations: { |
Vuex官方解释:
一条重要的原则就是要记住 mutation 必须是同步函数。为什么?请参考下面的例子:
1 | mutations: { |
现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。
4.6 如果需求包含异步操作,而Mutation要求是同步的?
Vuex为我们提供了Action
,在Action
中可以进行异步操作。
在 mutation 中混合异步调用会导致你的程序很难调试。例如,当你调用了两个包含异步回调的 mutation 来改变状态,你怎么知道什么时候回调和哪个先回调呢?这就是为什么我们要区分这两个概念。在 Vuex 中,mutation 都是同步事务:
1 | store.commit('increment') |
4.7 使用常量替代 Mutation 事件类型
使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:
1 | // mutation-types.js |
1 | // store.js |
用不用常量取决于你——在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做。
五、Action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
取state为什么要使用computed?
为什么Mutation是异步的,action可以异步以及同步?
vue-devtools
通过this.$store.state.xxx能够对状态进行获取以及修改,那么getter、Mutation、action的存在的意义是什么?
mixins混入 p120
vue持久化问题