咳咳,各位老铁,双击屏幕666!今天咱们来唠唠 Vuex 里那个风骚的 dispatch 方法,看看它怎么玩转异步操作,还能支持 Promise 返回,简直是前端界的扛把子。
开场白:dispatch 的江湖地位
在 Vuex 的世界里,dispatch 就像一个总指挥,它负责接收各种指令(也就是 actions),然后把这些指令分发给相应的执行者。 简单来说,你想对 Vuex 的 state 做点什么,不能直接动手,得先通过 dispatch 告诉它。
dispatch 的基本用法:同步与异步的抉择
首先,咱们先复习一下 dispatch 的基本用法。它可以用来触发同步的 actions,也可以用来触发异步的 actions。
// store.js
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
},
async incrementAsync (context) {
await new Promise(resolve => setTimeout(resolve, 1000))
context.commit('increment')
}
}
})
// 组件中使用
store.dispatch('increment') // 同步
store.dispatch('incrementAsync') // 异步
上面的例子中,increment 是一个同步的 action,直接通过 context.commit 触发 mutation。 incrementAsync 是一个异步的 action,它使用 async/await 来模拟一个 1 秒的延迟,然后再触发 mutation。
深入源码:dispatch 的内部构造
好,现在咱们要开始扒 dispatch 的源码了。 Vuex 的源码其实并不复杂,但是要理解它的设计思想,需要一点耐心。
// Vuex/src/store.js
// 重点关注这个 dispatch 方法
dispatch (_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
const entry = this._actions[type]
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
try {
this._mutating = true // 标记正在mutating,防止在mutation过程中dispatch
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
} finally {
this._mutating = false // 恢复状态
}
}
这段代码看起来有点抽象,咱们一步一步来解释。
-
unifyObjectStyle:统一对象风格这个函数的作用是把两种不同的
dispatch调用方式统一起来。 Vuex 支持两种dispatch的方式:- 字符串形式:
store.dispatch('increment') - 对象形式:
store.dispatch({ type: 'increment' })
unifyObjectStyle函数会把字符串形式的调用转换成对象形式,方便后续处理。// Vuex/src/utils.js export function unifyObjectStyle (type, payload, moduleNamespace) { if (typeof type === 'string') { return { type, payload } } else if (typeof type === 'object' && type.type) { return { type: type.type, payload: payload || type.payload } } else { throw new Error(`Expect action's type to be string or object with type attribute, but got ${typeof type}.`) } }简单来说,如果你的
dispatch调用是store.dispatch('increment'),那么unifyObjectStyle会把它变成{ type: 'increment', payload: undefined }。 - 字符串形式:
-
this._actions[type]:查找对应的actionthis._actions是一个对象,它存储了所有注册的actions。 每个action的type作为key,对应的value是一个handler函数或者一个handler函数的数组。如果找不到对应的
action,Vuex 会在开发环境下打印一个错误信息。 -
entry.length > 1:处理多个handler一个
action可以对应多个handler函数。 这种情况通常发生在插件中,不同的插件可能需要对同一个action进行处理。如果一个
action对应多个handler函数,Vuex 会使用Promise.all来并发执行这些handler函数。 这意味着所有的handler函数都会并行执行,只有当所有的handler函数都执行完毕后,dispatch才会返回。如果只有一个
handler函数,Vuex 会直接执行这个handler函数。 -
handler(payload):执行action这行代码是真正执行
action的地方。handler函数接收一个payload参数,这个参数就是你在dispatch的时候传递的参数。handler函数的返回值可以是任意值,也可以是一个Promise对象。 如果handler函数返回一个Promise对象,Vuex 会等待这个Promise对象 resolve 后再返回。 -
try...finally:确保状态恢复this._mutating = true和this._mutating = false这两行代码是为了防止在mutation过程中调用dispatch。 如果在mutation过程中调用dispatch,Vuex 会抛出一个错误。try...finally语句确保无论action执行成功还是失败,this._mutating的状态都会被恢复。
dispatch 如何处理异步性?
关键就在于 handler 函数的返回值。如果 handler 函数返回一个 Promise 对象,dispatch 会等待这个 Promise 对象 resolve 后再返回。
这使得我们可以使用 async/await 来编写异步的 actions,并且可以很方便地获取异步操作的结果。
// actions.js
const actions = {
async fetchData (context) {
const response = await fetch('/api/data')
const data = await response.json()
context.commit('setData', data)
return data // 返回Promise resolve的值
}
}
// 组件中使用
store.dispatch('fetchData').then(data => {
console.log('数据加载完成:', data)
})
在上面的例子中,fetchData 是一个异步的 action,它使用 fetch API 来获取数据。 fetch API 返回一个 Promise 对象,await 关键字会等待这个 Promise 对象 resolve 后再执行后面的代码。
fetchData 函数返回获取到的数据,dispatch 方法会返回一个 Promise 对象,这个 Promise 对象会在 fetchData 函数 resolve 后 resolve,并且 resolve 的值就是 fetchData 函数的返回值。
dispatch 的 Promise 支持
dispatch 方法返回一个 Promise 对象,这意味着我们可以使用 then、catch、finally 等方法来处理异步操作的结果。
store.dispatch('fetchData')
.then(data => {
console.log('数据加载成功:', data)
})
.catch(error => {
console.error('数据加载失败:', error)
})
.finally(() => {
console.log('加载完成')
})
dispatch 的多 handler 处理
前面提到过,一个 action 可以对应多个 handler 函数。 在这种情况下,dispatch 会使用 Promise.all 来并发执行这些 handler 函数。
// plugins/logger.js
const logger = store => {
store.subscribeAction({
before: (action, state) => {
console.log(`[logger] action ${action.type} started`)
},
after: (action, state) => {
console.log(`[logger] action ${action.type} finished`)
}
})
}
// store.js
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
},
plugins: [logger]
})
// 组件中使用
store.dispatch('increment')
在上面的例子中,logger 插件订阅了所有的 actions,并在 action 执行前后打印日志。 当 dispatch('increment') 被调用时,increment action 和 logger 插件的 before 和 after handler 都会被执行。 Vuex 会使用 Promise.all 来并发执行这些 handler 函数。
总结:dispatch 的核心价值
dispatch 方法是 Vuex 的核心之一,它负责接收指令、分发指令、处理异步操作,并且支持 Promise 返回。 它把同步和异步的操作都统一起来,使得我们可以很方便地管理 Vuex 的 state。
| 特性 | 描述 |
|---|---|
| 统一对象风格 | 使用 unifyObjectStyle 函数,将字符串形式和对象形式的 dispatch 调用统一起来。 |
| 异步处理 | 如果 action handler 返回一个 Promise,dispatch 会等待这个 Promise 完成。 |
多 handler |
允许一个 action 对应多个 handler,并使用 Promise.all 并发执行这些 handler。 |
| 状态保护 | 使用 try...finally 语句确保在 mutation 过程中不会调用 dispatch,防止状态污染。 |
| Promise 支持 | dispatch 方法返回一个 Promise 对象,允许使用 then、catch、finally 等方法处理异步操作的结果。 |
彩蛋:一些需要注意的地方
- 不要在
mutation过程中调用dispatch。 Vuex 会抛出一个错误。 - 尽量使用
async/await来编写异步的actions。 这样可以使代码更简洁、更易读。 - 如果一个
action对应多个handler函数,要注意这些handler函数之间的依赖关系。 因为它们是并发执行的。
好了,今天的讲座就到这里。希望大家对 Vuex 的 dispatch 方法有了更深入的了解。 记住,源码面前,了无秘密! 下次再见!