Vuex 除了提供的存取能力,还提供了一种插件能力,可以监控 store 的变化过程来做一些事情
Vuex 的 store 接受 plugins 选项,在实例化 Store 的时候可以传入插件,它是一个数组,然后在执行 Store 构造函数的时候,会执行这些插件

  1. const {
  2. plugins = [],
  3. strict = false
  4. } = options
  5. // apply plugins
  6. plugins.forEach(plugin => plugin(this))

在实际项目中,用到的最多的就是 Vuex 内置的 Logger 插件,它能够帮助追踪 state 变化,然后输出一些格式化日志
下面就来分析这个插件的实现

Logger 插件

Logger 插件的定义在 src/plugins/logger.js 中

  1. export default function createLogger ({
  2. collapsed = true,
  3. filter = (mutation, stateBefore, stateAfter) => true,
  4. transformer = state => state,
  5. mutationTransformer = mut => mut,
  6. actionFilter = (action, state) => true,
  7. actionTransformer = act => act,
  8. logMutations = true,
  9. logActions = true,
  10. logger = console
  11. } = {}) {
  12. return store => {
  13. let prevState = deepCopy(store.state)
  14. if (typeof logger === 'undefined') {
  15. return
  16. }
  17. if (logMutations) {
  18. store.subscribe((mutation, state) => {
  19. const nextState = deepCopy(state)
  20. if (filter(mutation, prevState, nextState)) {
  21. const formattedTime = getFormattedTime()
  22. const formattedMutation = mutationTransformer(mutation)
  23. const message = `mutation ${mutation.type}${formattedTime}`
  24. startMessage(logger, message, collapsed)
  25. logger.log('%c prev state', 'color: #9E9E9E; font-weight: bold', transformer(prevState))
  26. logger.log('%c mutation', 'color: #03A9F4; font-weight: bold', formattedMutation)
  27. logger.log('%c next state', 'color: #4CAF50; font-weight: bold', transformer(nextState))
  28. endMessage(logger)
  29. }
  30. prevState = nextState
  31. })
  32. }
  33. if (logActions) {
  34. store.subscribeAction((action, state) => {
  35. if (actionFilter(action, state)) {
  36. const formattedTime = getFormattedTime()
  37. const formattedAction = actionTransformer(action)
  38. const message = `action ${action.type}${formattedTime}`
  39. startMessage(logger, message, collapsed)
  40. logger.log('%c action', 'color: #03A9F4; font-weight: bold', formattedAction)
  41. endMessage(logger)
  42. }
  43. })
  44. }
  45. }
  46. }
  47. function repeat (str, times) {
  48. return (new Array(times + 1)).join(str)
  49. }
  50. function pad (num, maxLength) {
  51. return repeat('0', maxLength - num.toString().length) + num
  52. }

插件函数接收的参数是 store 实例,它执行了 store.subscribe 方法,先来看一下 subscribe 的定义

  1. // 往this._subscribers去添加一个函数,并返回一个unsubscribe的方法
  2. subscribe (fn, options) {
  3. return genericSubscribe(fn, this._subscribers, options)
  4. }
  5. function genericSubscribe (fn, subs, options) {
  6. if (subs.indexOf(fn) < 0) {
  7. options && options.prepend
  8. ? subs.unshift(fn)
  9. : subs.push(fn)
  10. }
  11. return () => {
  12. const i = subs.indexOf(fn)
  13. if (i > -1) {
  14. subs.splice(i, 1)
  15. }
  16. }
  17. }

而在执行 store.commit 的方法的时候,会遍历 this._subscribers 执行它们对应的回调函数

  1. commit (_type, _payload, _options) {
  2. // check object-style commit
  3. const {
  4. type,
  5. payload,
  6. options
  7. } = unifyObjectStyle(_type, _payload, _options)
  8. const mutation = { type, payload }
  9. const entry = this._mutations[type]
  10. if (!entry) {
  11. if (__DEV__) {
  12. console.error(`[vuex] unknown mutation type: ${type}`)
  13. }
  14. return
  15. }
  16. this._withCommit(() => {
  17. entry.forEach(function commitIterator (handler) {
  18. handler(payload)
  19. })
  20. })
  21. this._subscribers
  22. .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
  23. .forEach(sub => sub(mutation, this.state))
  24. if (
  25. __DEV__ &&
  26. options && options.silent
  27. ) {
  28. console.warn(
  29. `[vuex] mutation type: ${type}. Silent option has been removed. ` +
  30. 'Use the filter functionality in the vue-devtools'
  31. )
  32. }
  33. }

回到 Logger 函数,它相当于订阅了 mutation 的提交,它的 prevState 表示之前的 state,nextState 表示提交 mutation 后的 state,这两个 state 都需要执行 deepCopy 方法拷贝一份对象的副本,这样对他们的修改就不会影响原始 store.state
接下来就构造一些格式化的消息,打印出一些时间消息 message, 之前的状态 prevState,对应的 mutation 操作 formattedMutation 以及下一个状态 nextState
最后更新 prevState = nextState,为下一次提交 mutation 输出日志做准备

Vuex 从设计上支持了插件,很好地从外部追踪 store 内部的变化,Logger 插件在开发阶段也提供了很好地指引作用
当然也可以自己去实现 Vuex 的插件,来帮助实现一些特定的需求