RadiomM
文章22
标签13
分类1
常用的vuex操作

常用的vuex操作

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'
])
}
}

结语

其实比较难记得还是这个多个模块时候的操作,当然,这些东西都是熟能生巧的,可以使因为我平时使用的少,所以突然用起来的时候就会变得相当吃力。所以这次我是痛定思痛,直接写了一篇文章,熟悉里面的用法了。