vuex在我们开发大型复杂项目的时候,是非常必须使用的一个vue周边插件。在想写这篇文章的时候,其实是最近我在使用vuex的时候,经常会遇到某些操作不会使用,而去百度搜索的情况,那么竟然如此,我就总结一下这些东西,加深印象,从而熟练的使用vuex。
什么是vuex
虽然如此,为了保证没有使用过vuex的同学也一样能看的明白,我还是非常负责任(水文章)的介绍一下vuex。
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
简单的说,就是一个存储数据的库,再过多的介绍就不写了这里贴一下地址:vuex
vuex简单使用
在这里引用官方的例子,如何创建一个简单的store。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { createApp } from 'vue' import { createStore } from 'vuex'
// 创建一个新的 store 实例 const store = createStore({ state () { return { count: 0 } }, mutations: { increment (state) { state.count++ } } })
const app = createApp({ /* 根组件 */ })
// 将 store 实例作为插件安装 app.use(store)
|
现在,你可以通过 store.state 来获取状态对象,并通过 store.commit 方法触发状态变更:
1 2 3
| store.commit('increment')
console.log(store.state.count) // -> 1
|
在 Vue 组件中, 可以通过 this.$store 访问store实例。现在我们可以从组件的方法提交一个变更:
1 2 3 4 5 6
| methods: { increment() { this.$store.commit('increment') console.log(this.$store.state.count) } }
|
再次强调,我们通过提交 mutation 的方式,而非直接改变 store.state.count,是因为我们想要更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外,这样也让我们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。有了它,我们甚至可以实现如时间穿梭般的调试体验。
由于 store 中的状态是响应式的,在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。触发变化也仅仅是在组件的 methods 中提交 mutation。
State
我们经常用vuex的时候,访问store的state可以用以下的方式.
1 2 3 4 5 6 7
| export default { data() { return { a: this.$store.state.a } } }
|
在我们引入的时候,vuex内容将挂载到vue实例的$store上。当然,在我们需要访问多个的时候,不可能直接全部这样调用的,所以就有了mapState这个函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { mapState } from 'vuex'
export default { data() { return {} }, computed: { ...mapState({ count: state => state.count, // 传字符串参数 'count' 等同于 `state => state.count` countAlias: 'count', }) } }
|
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
1 2 3 4 5 6
| computed: { ...mapState([ // 映射 this.count 为 store.state.count 'count' ]) }
|
Getter其实可以看做是store的一个计算属性,用法跟state大同小异,这里就不多叙述。
Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
1 2 3 4 5 6 7 8 9 10 11
| const store = createStore({ state: { count: 1 }, mutations: { increment (state) { // 变更状态 state.count++ } } })
|
你不能直接调用一个 mutation 处理函数。这个选项更像是事件注册:“当触发一个类型为 increment 的 mutation 时,调用此函数。”要唤醒一个 mutation 处理函数,你需要以相应的 type 调用 store.commit 方法:
1
| store.commit('increment')
|
那么传参怎么做啊?其实很简单:
1 2 3 4 5
| mutations: { increment (state, n) { state.count += n } }
|
1
| store.commit('increment', 10)
|
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:
1 2 3 4 5
| mutations: { increment (state, payload) { state.count += payload.amount } }
|
1 2 3
| store.commit('increment', { amount: 10 })
|
有一点是需要注意的,Mutation 必须是同步函数为什么?请参考下面的例子:
1 2 3 4 5 6 7
| mutations: { someMutation (state) { api.callAsyncMethod(() => { state.count++ }) } }
|
现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。
Action
Action 通过 store.dispatch 方法触发:
1
| store.dispatch('increment')
|
乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作:
1 2 3 4 5 6 7
| actions: { incrementAsync ({ commit }) { setTimeout(() => { commit('increment') }, 1000) } }
|
来看一个更加实际的购物车示例,涉及到调用异步 API 和分发多重 mutation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| actions: { checkout ({ commit, state }, products) { // 把当前购物车的物品备份起来 const savedCartItems = [...state.cart.added] // 发出结账请求,然后乐观地清空购物车 commit(types.CHECKOUT_REQUEST) // 购物 API 接受一个成功回调和一个失败回调 shop.buyProducts( products, // 成功操作 () => commit(types.CHECKOUT_SUCCESS), // 失败操作 () => commit(types.CHECKOUT_FAILURE, savedCartItems) ) }
|
注意我们正在进行一系列的异步操作,并且通过提交 mutation 来记录 action 产生的副作用(即状态变更)。
Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?
首先,你需要明白 store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:
1 2 3 4 5 6 7 8 9 10
| actions: { actionA ({ commit }) { return new Promise((resolve, reject) => { setTimeout(() => { commit('someMutation') resolve() }, 1000) }) } }
|
现在你可以:
1 2 3
| store.dispatch('actionA').then(() => { // ... })
|
在另外一个 action 中也可以:
1 2 3 4 5 6 7 8
| actions: { // ... actionB ({ dispatch, commit }) { return dispatch('actionA').then(() => { commit('someOtherMutation') }) } }
|
Module
模块化这个就不多介绍了,就是将store分成几块的,有各自的state、mutation、action、getter。这里主要是介绍,怎么调用每个模块的state等等(主要介绍这个是因为我经常忘了这个怎么使用)。
在比较大型的项目中,store通常会有几个模块,举个例子, 一个中后台项目肯定有,user模块,测导航栏模块等等。看下面伪代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const moduleA = { state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... } }
const moduleB = { state: () => ({ ... }), mutations: { ... }, actions: { ... } }
const store = createStore({ modules: { a: moduleA, b: moduleB } })
|
假如我需要调用A模块的getters中的getUserInfo方法,那么我需要怎么操作呢?看下面代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { mapGetters } from 'vuex'
export default { data() { return {
} }, computed: { ...mapGetters('a',{ getUserInfo: 'getUserInfo' }) } }
|
也可以写成下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { mapGetters } from 'vuex'
export default { data() { return {
} }, computed: { ...mapGetters({ getUserInfo: 'user/getUserInfo' }) } }
|
当然也可以针对某个模块引用它的state, actions, getters, 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('user')
export default { computed: { // 在 `user` 中查找 ...mapState({ userinfo: state => state.userinfo, }) }, methods: { // 在 `user` 中查找 ...mapActions([ 'foo', 'bar' ]) } }
|
结语
其实比较难记得还是这个多个模块时候的操作,当然,这些东西都是熟能生巧的,可以使因为我平时使用的少,所以突然用起来的时候就会变得相当吃力。所以这次我是痛定思痛,直接写了一篇文章,熟悉里面的用法了。