超级简单易懂的Vuex入门使用指南

一、为什么使用Vuex

当我们进行前端开发时,不可避免要与页面中的数据打交道,而在VUE框架中,我们都知道当在单个页面操作数据时,最基础的是使用data()方法中的返回数据,这部分数据可以在页面中直接获得,通过v-bind响应式在页面中渲染出来。

但是当我们需要在多个页面间获取组件状态进行数据交互的时候,data中返回出来的数据进行传递的过程显然不是很方便。这时候,Vuex就是一个帮助我们进行数据状态管理的有效的工具了。

Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension ,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

二、Vuex基础使用

1. Store

Vuex应用的核心是store(仓库),store是一个包含着组件状态信息的容器,当store中的状态发生变化时,相应的组件也会在同时得到高效的更新。

对于一个最简单的store来说,我们可以用下面的方法进行创建

1
2
3
4
5
6
7
8
9
10
11
12
const store = new Vuex.Store({
state: {
count: 0
},

mutations: {
increment (state) {
state.count++
}
}

})

我们可以看到store中包含两个部分,一个是state,state可以理解为我们存储起来的状态对象,另一个是mutation,mutation是一种修改state的方法,后面会提到。

2. State

前面说到,state中保存的数据就是我们存储起来的内容,从上面store的声明中也可以看出来数据是用键值对{count:0}的形式保存在store仓库中的,观察结构结构我们也可以发现,获取state的方法也很简单,通过store.state就可以直接获取到state中所有的状态对象了。

但是如何在vue组件中展示state状态呢?

(1) 获取state状态

由于vuex的状态存储是响应式的,从store中读取状态最简单的方法就是在计算属性中返回我们需要的状态

1
2
3
4
computed: {
count () {
return store.state.count
}

}

但是,这种模式导致组件依赖全局状态单例,简单来说,就是每次需要获取state中的数据时都要进行导入store仓库的工作。

(2)解决重复导入问题

为了解决上述重复导入的问题,vuex提供了另一种机制,将状态从根组件“注入”到每一个子组件中(使用Vue.use(Vuex)方法)

1
2
3
4
5
6
7
8
9
10
11
const app = new Vue({
el: '#app',
// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})

此时,所有的子组件都能够通过this.$store访问到这个store实例,从而获取到store中的state。

这时候,虽然可以获取到store了,但是出现了新的问题,当我们需要很多store的state中的数据时,重复使用计算属性中的函数返回方法显得十分复杂和冗余,那么这个应该怎么解决呢?

(3)mapState辅助函数

减少计算属性编写的复杂度当然也是有办法的,mapState辅助函数可以帮助我们生成计算属性,大大降低了我们编写函数的复杂度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

export default {
// ...
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,

// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',

// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}

在文件中首先引入vuex中的mapState方法,然后在computed的部分使用mapstate进行数据的获取。使用state=>state.count的方法修剪函数形式,使得代码更加简练,同时,单独传递字符串参数的形式也是等同的,我们可以根据自己的需要自行选择。

在这里,我们提到一种更简练的方法,就是不需要冒号前面的值进行变量名的声明,而是直接传递字符串参数完成映射(如下面的形式),但是这种形式个人不是很推荐,就感觉看起来不规格(?),所以我就不多描述

1
2
3
4
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
})

回到开始的话题,其实从上面的示例中我们也可以发现,如果需要使用this获取到vue文件中本身的状态,使用mapState显得不是很方便,仍旧需要常规函数的写法。

(4)对象展开运算符

mapState会返回一个对象,当我们需要将这个返回的对象和局部计算属性混合使用时,我们就需要工具函数将多个对象合并为一个,使得我们可以将最终的对象传递给computed属性,同时简化写法

1
2
3
4
5
6
7
computed: {
localComputed () { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}

3. Mutation

说完了state的获取,这时候我们下一步一定要学习的就是怎么对store中state进行修改。首先,Store中的state是不能被我们直接更改的,想要更改唯一的方法就是显示提交(commit),也就是使用mutation。

(1) mutation基础

每个mutation都有一个字符串的事件类型(type)和一个回调函数(handler),这个回调函数就是我们实际进行state修改的地方。它接受state作为第一个参数,显然,state中的所有数据都可以在回调函数进行修改

1
2
3
4
5
6
7
8
9
10
11
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})

(2)commit方法

mutation的格式我们理解了,那我们应该怎么使用这个回调函数呢?直接掉用吗?

答案当然是不是。

我们不可以直接调用handler,handler可以理解为当触发了一个事件类型为increment的mutation时,回调函数中的事件就会发生,这个触发的过程就是commit方法。

对于上面的increment函数,我们可以使用下面的语句在vue中进行触发

1
store.commit('increment')

(3)向mutation回调函数中传递参数

想要修改数据,传递新的数据给回调函数就是我们下一步思考的了,这个新数据我们称之为mutation的载荷(payload)

1
2
3
4
5
mutations: {
increment (state, n) {
state.count += n
}
}
1
store.commit('increment', 10)

在绝大多数情况下,载荷应该是一个对象,在实际应用中,如果想要传递多个参数,通常我们会选择将这些数据封装到一个对象(如json对象)中,再将这个对象作为荷载传递给相应的回调函数

(4) mapMutations辅助函数

不用多说可能大家也能看出来,这个和mapState是一个作用,都是便于在vue文件中引入store中的state和mutation,使用方法其实也是一样的,区别只是在于由于mutation的主要工作内容是其中的回调函数,所以它的获取不需要使用计算属性,而是使用method即可,这里就不过多阐述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { mapMutations } from 'vuex'

export default {
// ...
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
}

4. Action

Action和mutation是类似的,主要的区别在于action可以处理系统中的异步回调方法,而mutation只能是同步的

(1)Action与mutation

Action函数接受一个与store实例具有相同方法和属性的context对象,所以在action中我们可以使用commit进行一个mutation的提交。

1
2
3
4
5
actions: {
increment ({ commit }) {
commit('increment')
}
}

这一点之所以被我放在action介绍的第一点,是因为这种在action作为异步调用方法,在其中使用commit方法进行state修改的写法通常被我们用来将api调用后获取到的数据与当前state进行交互。例如,将从服务器获取到的某数据赋值给state中的某变量。

多数情况下,我们在action中进行api的调用(如get方法),并在对应api调用的回调中将获取到的数据使用commit方法提交给mutation以便于组件状态state的修改

(2)action的使用

action与mutation写法类似,调用方法也是类似的,我们都需要使用一种特殊的触发方法进行action中的调用,而这次不应该使用commit,而是使用dispatch分发

1
store.dispatch('increment')

同理,dispatch也同样支持载荷的方式,具体方法与mutation完全一样,我就不过多赘述

1
store.dispatch('increment', 10)

(3)mapAction赋值函数

很好,写到这感觉道理大家应该都懂,我也就不多说,直接上代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { mapActions } from 'vuex'

export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}

(4) 异步方法

异步就会出现问题:action什么时候结束?想要等到结束之后再进行某种行为应该怎么办?
稍微熟悉前端的同学就应该知道,对于结束后的处理,我们都是使用then方法的,action也不例外,使用下面的方法就可以

1
2
3
store.dispatch('actionA').then(() => {
// ...
})

而这种方法的前提自然也就是要把需要的数据return出来,dispatch可以处理被触发的action的处理函数返回的promise,而且dispatch仍旧返回promise,action方法的写法如下

1
2
3
4
5
6
7
8
9
10
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}

此时在另外一个action中我们也可以进行如下的操作
actions: {

1
2
3
4
5
6
7
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}

涉及async/await的更复杂的组合也可以

1
2
3
4
5
6
7
8
9
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}

5. Getter

getter主要是用来获取state数据的,有计算属性在这个我实在是很少用到,感兴趣的同学可以去查看官方文档,就不说啦

6. modules

学会使用vuex的store、state、action、commit,基础的入门就已经完成啦,而modules的模块化也仅仅是指在一个store文件中引入其他store,一个import就做到了!

额外需要注意的可能就是辅助函数可以新增一个参数表示快捷的访问子store的内容(事实上也不是必须的),感兴趣的大家自己学习就行啦~

三、Vuex使用中需要注意的事

(1)Vuex是好用的模式,但是并不意味着所有数据都要使用vuex进行保存,滥用vuex造成代码冗长和不直观可是一点性价比都没有的,这也是单页面应用并不推荐使用vuex的原因。我们要根据代码环境判断是否有必要使用vuex保存某个状态数据。

(2)Mutation以及action文件在编写的过程中,我们通常使用常量代替其事件类型,比如下面的表示方式,很像是给文件添加了一个目录,这样的好处是代码的合作者可以很清晰地总结mutation文件中声明的所有事件类型,我们自己在使用的时候也是更加方便的

1
2
3
4
5
export const SOME_MUTATION = 'SOME_MUTATION'
// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
[SOME_MUTATION] (state) {
// mutate state
}

(3)Vuex中的状态是响应式的,但是对于数组的响应过程由于DOM的渲染,只对长度敏感,也就是说,对于数组的状态,vuex只能对长度变化作出反应,并进行增加或截取,具体的数据内容是获取不到的,想要渲染的话要使用Vue.set方法(事实上我还是不行,等待大佬解答)

最后

学习和使用vue做好前端开发是个说快很快说慢也很慢的过程,共勉。

0%