vuex
最近进入了一个新项目组,前端框架选择vue进行开发,数据的状态管理选择用vuex。本篇随笔中的代码采用vuex官网提供的购物车案例。
项目结构
├── index.html
├── main.js
├── api
│ └── shop.js # 抽取出API请求
├── components
│ ├── App.vue # 根级别的页面
│ ├── Cart.vue # 购物车组件
│ └── ProductList.vue # 产品组件
│
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
├── mutation-types.js # mutation事件类型
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
核心概念
Vuex有五个核心概念,分别是:State、Getter、Mutation、Action和Module
State
由于Vuex的状态存储是响应式的,所以从store实例中读取状态最简单的方式是在计算属性中返回某个状态
//product.js
const state = {
all: [{
'id': 1, 'title': 'iPad 4 Mini', 'price': 500.01
},{
'id': 2, 'title': 'H&M T-Shirt White', 'price': 10.99
}]
}
export default {
state
}
//productList.vue
import store from '../store/index'
<template>
<ul>
<li v-for="p in products">
{{ p.title }} - {{ p.price | currency }}
</li>
</ul>
</template>
<script>
export default {
computed: {
products() {
return store.state.all
}
})
}
</script>
当一个组件需要获取多个状态时,可以通过mapState
辅助函数帮助我们生成计算属性
//改造productList.vue
import { mapState } from 'vuex'
export default {
computed: mapState({
// 箭头函数可使代码更简练
products: state => state.all,
// 传字符串参数 'all' 等同于 `state => state.all`
//products: 'all',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
/* products (state) {
return state.all + this.localCount
} */
})
}
如果计算属性名和state子节点名字相同,也可以传递一个字符串数组
computed: mapState([
// 映射 this.all 为 store.state.all
'all'
])
如果想要与局部计算属性混合使用,则可以通过对象展开运算符做到这一点
computed: {
localComputed () { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
Getter
Getter相当于store实例的计算属性,Getter的返回值会根据它的依赖被缓存起来,只有依赖发生改变,才会重新计算。
Getter接受State作为第一个参数,其他的getter作为第二个参数,同时也会暴露为store.getters对象
//products.js
const getters = {
allProducts: (state, getter) => state.all
}
export default {
state,
getters
}
//productList.vue
computed: {
allProducts() {
return this.$store.getters.allProducts
}
}
同样,Getter也有辅助函数mapGetters
,它的作用是将store中的getter映射到局部计算属性,使用方法与mapState
一样。
Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 Mutation。
每个 Mutation 都有一个字符串的 事件类型 (type) 和 一个回调函数(handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数,通过store.commit可以传递第二个参数,也就是载荷(Payload)
// project.js
// 可以使用常量代替Mutation事件类型
const mutations = {
[types.RECEIVE_PRODUCTS] (state, { products }) {
state.all = products
},
[types.ADD_TO_CART] (state, { id }) {
state.all.find(p => p.id === id).inventory--
}
}
// actions
const actions = {
getAllProducts ({ commit }) {
shop.getProducts(products => {
commit(types.RECEIVE_PRODUCTS, { products })
})
}
}
// mutation-types.js
export const ADD_TO_CART = 'ADD_TO_CART'
export const RECEIVE_PRODUCTS = 'RECEIVE_PRODUCTS'
Mutation也有辅助函数mapMutations
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必须是同步函数,如果想包含异步操作,那么必须要使用Action
Action
Action和Mutation有两点不同:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
actions: {
getAllProducts ({ commit }) {
commit('types.types.RECEIVE_PRODUCTS')
}
}
Action通过store.dispatch
方法触发
store.dispatch('getAllProducts')
在组件中分发Action,我们可以使用mapActions
辅助函数将组件的methods映射为store.dispatch
调用,使用方法同mapMutations
。
Module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
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 的状态
默认情况下,模块内部的action、mutation、getter是注册在全局命名空间的,如果需要模块被更好的封装,那么可以通过添加namespaced: true
的方式使其成为命名空间模块
启用了命名空间的 getter 和 action 会收到局部化的 getter
,dispatch
和 commit
。