Vuex

官网传送门

概述

何为 Vuex ?

Vuex 专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 Vue 应用中多个组件的共享状态进行集中式的管理,也是一种组件间通信的方式,适用于任意组件间通信

何时用 Vuex ?

  • 多个组件依赖于同一状态
  • 来自不同组件的行为需要变更同一状态

Vuex 工作原理图:

Vuex工作原理

官方 Vuex 项目结构示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块

Vuex 核心概念

state

  • Vuex 管理的状态对象
  • 唯一的

actions

  • 值为一个对象,包含多个响应用户动作的回调函数
  • 通过 commit()触发 mutation 中函数的调用,间接更新 state
  • 可包含异步代码

mutations

  • 值为一个对象,包含多个直接更新 state 的方法
  • 不能写异步代码,只能单纯地操作 state

getters

  • 值为一个对象,包含多个用于返回数据的函数
  • 类似于计算属性,getters 返回的数据依赖于 state 的数据

modules

  • 一个 module 是一个 store 的配置对象,与一个组件对应

搭建 Vuex 环境

安装 Vuex:npm install vuex@3 --save

:::warning
注意:Vue2 安装 Vuex3,Vue3 安装 Vuex4,版本需对应。
:::

创建文件 src/store/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

const actions = {}
const mutations = {}
const state = {}

export default new Vuex.Store({
actions,
mutations,
state,
})

main.js 配置 store

1
2
3
4
5
6
7
import store from './store'

new Vue({
el: '#app',
store,
render: (h) => h(App),
})

基本使用

组件实例与 ActionsMutations 对话:

  • 若没有网络请求或其他业务逻辑,组件中也可以越过 actions ,即不写dispatch,直接编写commit
1
2
3
4
5
6
7
8
9
10
11
methods: {
increment() {
this.$store.commit('ADD', this.number)
},
incrementOdd() {
this.$store.dispatch('addOdd', this.number)
},
incrementAsync() {
this.$store.dispatch('addAsync', this.number)
}
}

定义 ActionsMutations

  • context 是一个迷你版的 store,可访问 dispatch, commit 方法和 state
  • mutations 的动作类型一般用大写,与 actions 区分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

const actions = {
// context 是一个迷你版的 store
// 可访问 dispatch, commit 方法和 state
addOdd(context, value) {
if (context.state.sum % 2 !== 0) {
context.commit('ADD', value)
}
},
addAsync(context, value) {
setTimeout(() => {
context.commit('ADD', value)
})
},
}
const mutations = {
// mutations 的动作类型一般用大写,与 actions 区分
ADD(state, value) {
state.sum += value
},
}

const state = {
sum: 0,
}

export default new Vuex.Store({
actions,
mutations,
state,
})

组件访问 Vuex 的数据:

1
<p>{{ $store.state.sum }}</p>

getters 的使用

  • state 中的数据需要经过加工后再使用时,可以使用 getters 加工
  • 它不是必须的,当加工逻辑复杂且需要复用时,可以考虑使用
  • stategetters 的关系有点像 datacomputed 的关系
  • 组件读取:$store.getters.bigSum
1
2
3
4
5
6
7
8
9
10
11
12
13
...
const getters = {
bigSum(state) {
return state.sum * 10
}
}

export default new Vuex.Store({
actions,
mutations,
state,
getters
})

四个 mapXxx 方法

mapState()

  • state 状态映射为计算属性
  • 对象写法:键为自取的计算属性名,值为对应的状态(必须为字符串)
  • 数组写法:当键值同名,可直接写状态名(字符串)
  • 函数返回一个对象:{sum: f, price: f}
  • 注意对象的 ...{} 展开写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { mapState } from 'vuex'

computed: {
// 手动写法
sum() {
return this.$store.state.sum
},
price() {
return this.$store.state.price
},

// 对象写法
...mapState({sum: 'sum', price: 'price'}),

// 数组写法
...mapState(['sum', 'price'])
}

mapGetters

  • getters 的数据映射为计算属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { mapGetters } from 'vuex'

computed: {
bigSum() {
return this.$store.getters.bigSum
},
double() {
return this.$store.getters.double
},

// 对象写法
...mapGetters({bigSum: 'bigSum', double: 'double'}),

// 数组写法
...mapGetters(['bigSum', 'double']),
}

mapActions

  • 生成与 actions 对话的函数,即包含 $store.dispatch()
  • mapActions 生成的函数不会传入参数,需要在调用时手动传入数据,不传参默认传入 $event
  • 数组写法要注意函数名和 actions 动作类型同名,调用时勿写错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { mapActions } from 'vuex'

methods: {
// 手动写法
incrementOdd() {
this.$store.dispatch('addOdd', this.number)
},
incrementAsync() {
this.$store.dispatch('addAsync', this.number)
},

// 对象写法
...mapActions({incrementOdd: 'addOdd', incrementAsync: 'addAsync'}),

// 数组写法
...mapActions(['addOdd', 'addAsync']),
}
1
<button @click="incrementOdd(number)">奇数+1</button>

mapMutations

  • 生成与 mutations 对话的函数,即包含 $store.commit()
  • 同样注意传递参数,以及数组形式函数名的问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { mapMutations } from 'vuex'

methods: {
increment() {
this.$store.commit('ADD', this.number)
},
decrement() {
this.$store.commit('SUB', this.number)
},

// 对象写法
...mapMutations({increment: 'ADD', decrement: 'SUB'}),

// 数组写法
...mapMutations(['ADD', 'SUB']),
}

Vuex 模块化&命名空间

让代码更好维护,让多种数据分类更加明确,每一类数据及其相关操作对应一个 store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// src/store/index.js
const countAbout = {
// 开启命名空间
namespaced: true,
state: {
sum: 0
},
actions: {...},
mutations: {...},
getters: {
bigSum(state) {
return state.sum * 10
}
}
}
const personAbout = {
// 开启命名空间
namespaced: true,
state: {
personList: []
},
actions: {...},
mutations: {...},
getters: {...}
}

export default new Vuex.Store({
modules: {
countAbout,
personAbout
}
})

开启命名空间后,组件中读取 state 数据:

1
2
3
4
5
// 直接读取
this.$store.state.personAbout.personList

// mapState 读取
...mapState('countAbout',['sum','school']),

开启命名空间后,组件中读取 getters 数据:

1
2
3
4
5
// 直接读取
this.$store.getters['countAbout/bigSum']

// mapGetters 读取:
...mapGetters('countAbout',['bigSum'])

开启命名空间后,组件中调用 dispatch

1
2
3
4
// 直接 dispatch
this.$store.dispatch('countAbout/addODdd', this.number)
// 借助 mapActions
...mapActions('countAbout', {incrementOdd:'addOdd', incrementWait:'addAsync'})

开启命名空间后,组件中调用 commit

1
2
3
4
// 直接 commit
this.$store.commit('personAbout/ADD_PERSON', person)
// 借助 mapMutations
...mapMutations('countAbout',{increment:'ADD',decrement:'SUB'}),