logo头像

王者风范 自由洒脱

Vuex

本文于1048天之前发表,文中内容可能已经过时。

Vuex

关于Vuex

Vuex是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新,就像是一个针对浏览器的小型数据库一样。

核心部分

1.state:驱动应用的数据源
2.view:以声明方式将state映射到视图
3.actions:响应在view上的用户输入导致的状态变化

image

核心概念

Store

store基本上就是一个容器,它包含着你的应用中大部分的状态 (state)
store和普通的全局变量的区别是:
1.Vuex 的状态存储是响应式的。
2.不能直接改变 store 中的状态。改变 store 状态唯一的途径是显式地提交 (commit) mutation

State

Vuex是单一状态树,拥有唯一的数据源。但这并不意味着它与模块化相冲突
获取Vuex中state状态的方法

1
this.$store.state.a

或使用辅助函数mapState

1
2
3
4
5
6
7
8
9
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

export default {
// ...
computed: {
...mapState(['a','b','c']),
}
}


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

Getter

getter可以认为是 store 的计算属性,返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//Getter 接受 state 作为其第一个参数:
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
// Getter 也可以接受其他 getter 作为第二个参数:
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
},
// 可以让 getter 返回一个函数,来实现给 getter 传参
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
})

获取Vuex中getter状态的方法

1
2
3
4
5
6
this.$store.getters.doneTodos
// -> [{ id: 1, text: '...', done: true }]
this.$store.getters.doneTodosCount
// -> 1
this.$store.getters.getTodoById(2)
// -> { id: 2, text: '...', done: false }

或使用辅助函数mapGetters

1
2
3
4
5
6
7
8
import { mapGetters } from 'vuex'

export default {
// ...
computed: {
...mapGetters(['doneTodosCount','anotherGetter'])
}
}

1
2
//如果想将一个 getter 属性另取一个名字,使用对象形式
mapGetters({doneCount: 'doneTodosCount'})

Mutation

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation,mutation 非常类似于事件

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

要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法:

1
this.$store.commit('increment',10)

或者使用辅助函数mapMutations

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

使用常量替代 Mutation 事件类型

这是各种 Flux 实现中是很常见的模式,可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然

1
2
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'

1
2
3
4
5
6
7
8
9
10
11
12
13
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
state: { ... },
mutations: {
// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
[SOME_MUTATION] (state) {
// mutate state
}
}
})

最后要强调一下–

Mutation必须是同步函数

因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。

Action

action和mutation的不同之处在于:
1.action提交的是mutation,而不是直接变更状态
2.action可以包含任意异步操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})

举个需要异步的栗子

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

使用Action

1
2
//作对比 -> this.$store.commit('getTodo')
this.$store.dispatch('increment')

或者使用辅助函数mapActions

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

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(() => {
// ...
})
1
2
3
4
5
6
7
8
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}

如果利用async/await,还可以:

1
2
3
4
5
6
7
8
9
10
11
// 假设 getData() 和 getOtherData() 返回的是 Promise

actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}

一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

Module

当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}

const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}

const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态