Vue Vuex

本文将介绍 Vuex,在 Vue 中用于集中式管理数据的工具。

一、什么是 Vuex?

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

  • 状态,又称为数据
  • Vuex 负责在 Vue 应用中进行数据管理
  • 有时候,多个组件需要访问相同的数据,此时需要考虑以下问题:
    • 组件应该都能获取数据
    • 组件应该都能修改数据
    • 数据应该是响应式的,能够在多个组件之间同步的
  • Vuex 将 Vue 应用中需要共享的数据抽取,放置于一个全局单例的容器之中,因此,各个组件都能够以相同的方式访问和修改相同的数据

二、安装

1. 下载或用 CDN 引入

1
<script src="URL"></script>

2. NPM

安装

1
npm install vuex --save

注册方法 1

在 main.js 中:

1
2
3
import Vuex from 'vuex'

Vue.use(Vuex)

注册方法 2(更好)

创建 store 文件夹,在其中创建 index.js 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
state: {

},
mutations: {

},
actions: {

},
getters: {

},
modules: {

}
})

export default store

在 main.js 中引入

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

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

三、简单使用

创建 store 文件夹,在其中创建 index.js 文件,注册 store ,放置数据并向外暴露:

1
2
3
4
5
6
7
8
9
10
11
12
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
state: {
属性名: 初始值
}
})

export default store

在 main.js 中引入:

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

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

在组件中获取 store 中的数据:

1
$store.state.属性名

不应该直接通过 $store.state.属性名 修改属性,应该提交并让 Vuex 处理,以便正确地追踪每一个属性的变化情况

四、State

1. 单一状态树

Vuex 使用单一状态树,简单来说,数据都放置于一个全局单例的容器之中,每个应用仅包含一个 store 实例。

这种做法将有利于监听数据状态,也有利于管理与维护。

2. 访问状态

(1) 直接访问

1
$store.state.状态名

(2) 通过计算属性

官方推荐使用计算属性获取状态

1
2
3
4
5
6
7
8
export default {
···
computed: {
计算属性名() {
return this.$store.state.状态名
}
}
}

3. mapState

(1) 说明

mapState() 方法的作用是:帮助程序员将 state 映射到计算属性中。

它接收一个对象,根据对象中的属性生成可以被 computed 识别的对象,并返回。

(2) 用法

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
mapState({
// 计算属性名称与状态名相同时的省略写法
'test1',

// 箭头函数可使代码更简练
test2: state => state.test3,

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

// 使用常规函数,以便获取组件内部的状态
countPlusLocalState (test6) {
return state.test7 + this.localCount
}
})

等效于:

{
test1 () {
return this.$store.state.test1
},
test2 () {
return this.$store.state.test3
},
test4 () {
return this.$store.state.test5
},
test6 () {
return this.$store.state.test7 + this.localCount
}
}

(3) 和其它计算属性共存

ES6 返回的是一个对象,为了与其它计算属性共存,可以利用 ES6 的扩展运算符。

1
2
3
4
5
6
7
computed: {
···

...mapState({
...
})
}

五、Getters

1. 说明

有时候,全局数据需要进行 “加工” 以获得想要的数据。

有一种解决方法是在组件中加工(在组件中使用计算属性访问全局数据,加工后返回)。

但如果有多个组件需要用到 “加工” 后的属性,更好的解决方式是将加工放置到 Vuex 中。

Vuex 提供了 Getters,它可以类比计算属性,属性会在加工后返回,并且返回值会缓存,当依赖值改变后才重新计算。

使用 Getters 也有其它好处,类比 Bean 中的 Getter,可以让状态被获取的方式单一化且便于后期维护

2. 基本使用

(1) 语法

1
2
3
4
5
6
7
8
9
10
11
12
13
const store = new Vuex.Store({
state: {
状态名: 初始值
},
getters: {
getter名: function(state) {
// 通过state.状态名调用状态参与运算
return ···运算···
}
}
})

$store.getters.getter名

(2) 示例

1
2
3
4
5
6
7
8
9
10
11
12
const store = new Vuex.Store({
state: {
counter: 999
},
getters: {
doubleCount(state) {
return state.counter * state.counter;
}
}
})

<p>doubleCount:{{ $store.getters.doubleCount }}</p>

3. 返回属性的 Getter

(1) 访问

1
store.getters.getter名

(2) 参数

不允许直接向 getters 传递参数,但 getters 可以接收两个参数,其中:

  • state:通过 state ,使 getters 能够访问 state 中的属性,访问方式为:

    1
    state.状态名
  • getters:通过访问其它派生属性,从而避免重复计算,使代码更加简洁

4. 返回方法的 Getter

(1) 作用

Getter 不仅可以从 state 和 getters 中获取,而且还可以通过方法的方式临时获取组件提供的其它参数,从而进行更复杂的更灵活的计算。

(2) 做法

让 Getter 返回一个方法,在方法中获取参数、计算结果并返回。

1
2
3
4
5
6
7
8
9
10
11
12
const store = new Vuex.Store({
···
getters: {
getter名(state) {
return function(参数列) {
return ···计算···
}
}
}
})

store.getters.getter名

(3) 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const store = new Vuex.Store({
state: {
counter: 999
},
getters: {
nFoldCount(state) {
return function(n) {
return n * state.counter
}
}
}
})

<p>doubleCount:{{ $store.getters.nFoldCount(3) }}</p>

(4) 不会缓存

返回方法的 Getter 和返回属性的 Getter 不同,其值并不会缓存,每次都重新计算。

5. mapGetter

和 mapState 类似,将 getter 映射到计算属性之中。

六、mutations

1. 作用

用于修改 store 中的 state。

2. 注册 mutation

(1) 语法

1
2
3
4
5
6
7
8
9
10
const store = new Vuex.Store({
state: {
状态名: 初始值
},
mutations: {
mutation名 (state) {
// 通过state.状态名改变变量值
}
}
})

state 并非从调用处传入,只需要在形参列中写上即可

(2) 示例

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

(3) 载荷

可以填入额外的参数,这个额外的参数被称为载荷(payload)

1
2
3
4
5
6
7
8
9
10
11
12
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
mutation名 (state, 形参) {
···
}
}
})

$store.commit('mutation名', 实参)

由于载荷只能有一个,因此最好将载荷设为对象,以便传递多个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
mutation名 (state, 形参) {
···
}
}
})

$store.commit('mutation名', {
属性名: 属性值
})

3. 调用 mutation

(1) 说明

不应该直接修改 state 中的数据,也不能够直接调用 mutation ,应该使用 commit 方法,并向 store 传递方法名,从而调用对应的方法修改数据。

(2) 语法

1
$store.commit('mutation名')

(3) 传入载荷

1
$store.commit('mutation名', 实参)

(4) 以对象形式传入参数

可以在调用 commit 方法时,以对象形式传入参数。

此时,

  • type 参数是必须的,填入方法名
  • 将载荷中的属性依次填入参数对象中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
mutation名 (state, data) {
···通过data.属性名访问属性···
}
}
})

$store.commit({
type: 'mutation名',
属性名: 属性值
})

4. 响应式规则

Vuex 的 store 中的属性是响应式的,但也需要遵守一些要求:

  • 响应式的属性必须在最开始就进行初始化

  • 需要为对象/数组添加新属性时,应该用 Vue.set

    1
    Vue.set(对象/数组, 属性名/数组下标, 值)
  • 需要为对象/数组删除属性时,应该用 Vue.delete

    1
    Vue.delete(对象/数组, 属性名/数组下标)
  • 不应该直接修改属性,应该通过 mutation (否则 Vue 将无法监听)

5. 必须是同步函数

mutation 必须是同步函数,mutation 只能够监听同步事务。如果需要处理异步操作,应该使用 actions 。

6. mapMutations

和 mapState 类似,将 mutations 映射到 methods 之中。

七、Actions

1. Actions 与 Mutations 的不同

  • Actions 提交 Mutation,通过 Mutation 来修改属性,并不直接修改属性
  • Actions 可以包含异步操作

2. 注册 Action

(1) 语法

1
2
3
4
5
6
7
8
9
10
11
12
13
const store = new Vuex.Store({
state: {
状态名: 初始值
},
actions: {
action名 (context) {
···
context.commit('mutation名')
}
}
})

$store.dispatch('action名')

(2) 参数

context 为 action 的默认参数,它和 store 并不等同,但拥有相同的内容。

(3) 载荷

actions 能够使用载荷,并且也能够以对象形式传入参数。

3. 调用 Action

(1) 语法

1
store.dispatch('action名')

(2) 传入载荷

1
store.dispatch('action名', 实参)

(3) 以对象形式传入参数

1
2
3
4
$store.dispatch({
type: 'action名',
属性名: 属性值
})

4. 异步 Action

Action 可以是异步的。

Action 可以返回 Promise,并且该 Promise 会被 store.dispatch() 返回。

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

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

5. mapActions

和 mapState 类似,将 actions映射到 methods 之中。

八、Modules

1. 什么是 Modules?

由于使用单一状态树,整个应用的所有共享属性都将放置在一个对象中,当应用十分复杂时,对象可能变得非常臃肿。

为了解决这一问题, Vue 允许我们将 store 分割成模块,每个模块将拥有自己的 state、getters、mutations、actions、modules。

2. 参数变化

  • getter 接收的参数将由全局状态对象变为局部状态对象
  • mutations 接收的参数将由全局状态对象变为局部状态对象
  • actions 仍然接收到 content,但 content 不再等同于全局状态对象,而是通过 content.state 获取局部状态对象,通过 content.rootState 获取全局状态对象

3. State 的访问

1
$store.state.模块名.属性名

4. Action、Mutation 和 Getter 的访问

(1) 默认方式

默认情况下,Action、Mutation 和 Getter 会注册在全局命名空间中。

即直接在全局就可以找到它们

此时,无需指定模块名就可以访问 Action、Mutation 和 Getter,访问方式和此前相同。

1
2
3
4
5
store.getters.getter名

store.commit('mutation名')

store.dispatch('action名')

(2) 命名空间方式

可以通过 namespaced: true 的方式使模块称为带命名空间的模块。

此时,需要指定模块名才能访问 Action、Mutation 和 Getter。

1
2
3
4
5
store.getters['模块名/getter名']

store.commit('模块名/mutation名')

store.dispatch('模块名/action名')

九、项目结构

可以将文件从 store/index.js 中抽离出去,方便管理与维护。

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

参考