
工欲善其事,必先利其器
项目中因为Vuex使用的不纯熟,导致各路api回炉重造。现在回顾一边Vuex的知识后再次回去重新整改相关的逻辑。Vuex的内容不多,所以我们可以简单而快速的回顾。
-
State
存储在 Vuex 中的数据和 Vue 实例中的 data 遵循相同的规则,你可以甚至把Vuex当作是一个插入全局的“组件”。
最简单的使用:
// 放入computed内,一旦vuex的数据更新,组件也可以接收到,立马改变相应的数据,如下,count是响应式的
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return store.state.count
}
}
}
Vuex 通过 store 选项,从根组件“注入”到每一个子组件(需要提前Vue.use(Vuex)):
const app = new Vue({
el: '#app',
// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
// 其子组件可以任意访问
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count // 访问Vuex实例属性
}
}
}
mapState 辅助函数
mapState并不是必须学习的内容, 但他能使你使用vuex的时候更加方便(类似于箭头函数一样,感觉上就比较爽)。但是某种意义上说,其实你是必须要使用它的。
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
}
})
}
// 同名的时候还可以简写为
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
-
Getter
你可以把他看为vuex版本的computed。
有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数:
// 只有一两个还好,数量一旦多起来,你会发现mapState给你带来的便利统统消失
// 你又再次的需要一次又一次的将state映射为computed
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
// 于是乎,出现了Getter,也就是vuex版本的computed
// 你可以像引入state一样引入它,这样你又可以解决一个麻烦了
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => { // getter可接受2个参数,这里只写了一个,第二个参数可以为其他getters
return state.todos.filter(todo => todo.done)
}
}
})
// getters第二个参数的用法
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
// 我们可以让getter返回一个方法,但这么做,就不会缓存结果了,也就是你其实就是调用了一个普通的函数,不会像computed。
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
// 我们来看看如何引入它
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
// mapGetter 也是必不可少的
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
-
Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
怎么提交,commit呗,英文直译,通俗易懂
store.commit('increment')
//我们可以提交参数的(废话)
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)
这个参数(载荷)最好是个对象
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
// 另外一种提交的方式(名曰对象提交大法)
store.commit({
type: 'increment',
amount: 10
})
Mutation 需遵守 Vue 的响应规则,Vuex其实更像一个Vue的组件,子类。所以你必须要对他的数据进行和Vue一样的规范。
-
最好提前在你的 store 中初始化好所有所需属性。
-
当需要在对象上添加新属性时,你应该
使用 Vue.set(obj, 'newProp', 123), 或者 以新对象替换老对象。例如,利用对象展开运算符我们可以这样写:
state.obj = { ...state.obj, newProp: 123 }
- Mutation 必须是同步函数
现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。
按道理来说,每一条 mutation 被记录,devtools 都需要捕捉到触发前和触发后状态。
然而,在上面的例子中 mutation 中的异步函数无法正常捕捉触发后状态:
因为当 mutation 触发的时候,回调函数最早也在下一循环才会被调用(事件循环机制),devtools 不知道什么时候回调函数实际上被调用——这将使得工具不可追踪,时间穿梭版的调试体验也无从谈起了。
- mapMutation 也是必不可少的
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')`
})
}
}
-
Action
你并不是必须使用Action的,请查看你的项目是否是大型项目或者多人配合。不然你可以在自己的逻辑里调用异步来提交mutation而不是将其写入action。单纯的说是不要为了action而action。
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
这使得他可以解决mutation无法解决的问题。(可以叫他异步mutation)。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment') //你现在可以把context视为store,你碰到问题了自然会意识到他与store的不同,或者你自行查阅
}
}
})
// 还记得commit嘛,这是触发mutation的方法,action使用的是dispatch(分发)
store.dispatch('increment')
以下是带入参数的写法
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})
我们来看一个购物车的实例 (这个例子我真的服了,懂的人估计都难看懂……)
actions: {
checkout ({ commit, state }, products) {
// 把当前购物车的物品存入变量
const savedCartItems = [...state.cart.added]
// 发出结账请求,并且清空购物车,此处的types.CHECKOUT_REQUEST对应一个字符串,例如: ‘CHECKOUT_REQUEST’
// 这个mutation将清空state.cart.added的数据。state.cart.added = []。
// 然后可能会改变一个变量,拉起一个“正在结算中。。。”的动画
commit(types.CHECKOUT_REQUEST)
// 发起结算的请求,可能使用的是ajax,因为此处使用了回调的方式。
shop.buyProducts(
products, // 未解,不知道干啥用的,这个例子好空洞。
// 成功操作
() => commit(types.CHECKOUT_SUCCESS)// 成功了,就把“正在结算中。。。”的动画关了
// 失败操作
() => commit(types.CHECKOUT_FAILURE, savedCartItems)// 将购物车内的变量重新放回。
)
}
}
- mapActions 也是必不可少的
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')`
})
}
}
- 组合 Action
store.dispatch 可以处理 action 函数返回的 Promise,并且 store.dispatch这个方法本身也返回 Promise:
// 可以看到这个action 函数返回的 Promise
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
// 诺,他自己也是个promise
store.dispatch('actionA').then(() => {
// ...
})
// 两个action组合 ,触发了A结束后,B才会触发
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
//使用async更加简洁了
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
我相信这个异步action东西相当一部分刚入门的小伙伴无法理解,还觉得写的什么垃圾玩意,确实写不好哈。。。不过指个方向,如果刚刚入门,完全可以不管action,这个只是vuex锦上添花的东西。未来某个时刻你会意识到promise的重要,并且学习完它后再回头看vuex,发现不过如此。如果你现在想去学习promise,推荐阮一峰老师的教程并且手动尝试去使用es5完善一个promise,这将会使你更加强大。
不说modules~