咳咳,各位老铁,双击屏幕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]
:查找对应的action
this._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
方法有了更深入的了解。 记住,源码面前,了无秘密! 下次再见!