Vuex 源码学习

版本:3.0.1

源码注释

学习源码过程中,我添加了一些注释,可以查看这个仓库open in new window,并将最新的一个提交(包含源码注释)与上一提交(Vuex 官方源码,使用的是 3.0.1 版本)对比,差异部分即为源码注释。

实现细节

store.state/getters 为什么是响应式的?

vuex的实现里,store上有一 Vue 实例store._vm

通过将store.state赋值为store._vm_data.$state,因而使得store.state获得了响应式的能力。

通过将store.getters赋值为store._vm.computed,使得每个getter成为了store._vm的计算属性,因此getter既拥有监听store._vm_data.$state改变(并重新计算出自身的新值)的能力,又拥有在自身值改变之后通知外部watcher的能力。

/**
 * 重置 store 实例的 vm 属性,并将 store 的 state 和 getters 分别映射到 vm 的 data 属性 和 computed 属性上,
 * 从而实现 getter 随 state 的变化而变化,以及 getter 的惰性获取能力,类似于 vue 实例的 computed 随 data 的变化而变化一样
 * @param {*} store store 实例
 * @param {*} state store 的根 state
 * @param {*} hot 用于热部署时
 */
function resetStoreVM (store, state, hot) {
  const oldVm = store._vm

  // bind store public getters
  store.getters = {}
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    computed[key] = () => fn(store)
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true // for local getters
    })
  })

  // use a Vue instance to store the state tree
  // suppress warnings just in case the user has added
  // some funky global mixins
  const silent = Vue.config.silent
  Vue.config.silent = true
  store._vm = new Vue({
    data: {
      // 将 store.state 作为 Vue 实例的 data 的 $state 属性,从而实现 store.state 是响应式的
      $state: state
    },
    // 将 store.getters 作为 Vue 实例的计算属性,从而实现 store.getter 随着 store._vm_data.$state 即 store.state 的改变重新计算出新值,若是值改变了,会通知外部依赖于该 getter 的 watcher
    computed
  })
  Vue.config.silent = silent

  // enable strict mode for new vm
  // 开启严格模式
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldVm) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldVm._data.$state = null
      })
    }
    Vue.nextTick(() => oldVm.$destroy())
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

模块的上下文对象 context

我们在注册模块时,每一模块对应着一Module类的实例,实例上有一属性context,其为模块的上下文对象,该对象的结构为:

{
  state,
  getters,
  commit,
  dispatch
}
1
2
3
4
5
6

对于启用了命名空间的模块来说,上面的stategetterscommitdispatch都是局部化的; 对于根模块以及“模块链上不存在启用命名空间的祖先模块”的模块来说,stategetterscommitdispatch都是全局的。

使用模块上下文对象的场景有很多,比如:

  • mapMutations/mapActions映射到组件方法上来提交mutation/分发action时,实际上使用的就是局部commitdispatch
  • getter函数的第一个参数是局部state,第二个参数是局部getters,第三个参数是全局state,第四个参数是全局getters
  • mutation回调函数的第一个参数context对象,就包括了上面的局部state(对于启用命名空间来说,state是指局部state
  • action回调函数的第一个参数context对象,就包含了上面的局部dispatchcommitgettersstate,以及全局rootGettersrootState

注意:根模块的局部stategetterscommitdispatch就是全局的stategetterscommitdispatch

function installModule (store, rootState, path, module, hot) {
  // ...
  const local = module.context = makeLocalContext(store, namespace, path)
  // ...
}
1
2
3
4
5
/**
 * make localized dispatch, commit, getters and state
 * if there is no namespace, just use root ones
 *
 * 创建绑定在给定命名空间上的局部 state、getters、commit、dispatch,若没有命名空间,返回根实例上的
 * @param {object} store store 实例
 * @param {string} namespace 命名空间
 * @param {object} path 模块路径
 */
function makeLocalContext (store, namespace, path) {
  const noNamespace = namespace === ''

  const local = {
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
        if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return
        }
      }

      // dispatch 的第三个参数 options 的 root 为 tree 时,分发根模块上的 action,否则分发命名空间模块上的 action
      return store.dispatch(type, payload)
    },

    commit: noNamespace ? store.commit : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
        if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {
          console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
          return
        }
      }

      // commit 的第三个参数 options 的 root 为 tree 时,提交根模块上的 mutation,否则提交命名空间模块上的 mutation
      store.commit(type, payload, options)
    }
  }

  // getters and state object must be gotten lazily
  // because they will be changed by vm update
  // getters、state 必须实时获取
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace)
    },
    state: {
      get: () => getNestedState(store.state, path)
    }
  })

  return local
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

可以从源码里看到,

  • 模块有命名空间时,局部commitdispatch实际上就是全局commitdispatch
  • 有命名空间时
    • 若调用commitdispatchoptions.roottrue,局部commitdispatch实际上就是调用全局commitdispatch
    • 否则,局部commitdispatch实际上也是调用全局commitdispatch,但是传入的type是带命名空间的

了解了局部的commitdispatch,我们再来学习下局部的stategetters,会发现有些不一样的地方。

源码里,获取state/getters都是通过存取描述符get来实时获取,这样每次获取的可能是不一样的值。

/**
 * 实时获取命名空间模块的 getters(遍历 store.getters,将符合命名空间的 getter 筛选出来)
 * @param {*} store store 实例
 * @param {*} namespace 命名空间
 */
function makeLocalGetters (store, namespace) {
  const gettersProxy = {}

  const splitPos = namespace.length
  // 每次获取时,遍历 store.getters 上的每个 getter,将符合命名空间的 getter 加入到 gettersProxy
  Object.keys(store.getters).forEach(type => {
    // skip if the target getter is not match this namespace
    if (type.slice(0, splitPos) !== namespace) return

    // extract local getter type
    const localType = type.slice(splitPos)

    // Add a port to the getters proxy.
    // Define as getter property because
    // we do not want to evaluate the getters in this time.
    Object.defineProperty(gettersProxy, localType, {
      get: () => store.getters[type],
      enumerable: true
    })
  })

  return gettersProxy
}

/**
 * 获取嵌套的子模块的 state
 * @param {*} state 根 state
 * @param {*} path 模块路径
 */
function getNestedState (state, path) {
  return path.length
    ? path.reduce((state, key) => state[key], state)
    : state
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

现在局部context对象已经有了,那么如果是如何将context注入到getter/mutationaction中去的呢?

安装模块时,会获取到模块的上下文,并在registerGetter/registerMutation/registerAction时作为参数传入,如此原始的getter/mutation/action便可以获取到context

function installModule (store, rootState, path, module, hot) {
  // ...

  const local = module.context = makeLocalContext(store, namespace, path)

  // 遍历 mutation,并注册
  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

  // 遍历 action,并注册
  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })

  // 遍历 getter,并注册
  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })

  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * 注册 getters
 * @param {*} store store 实例
 * @param {*} type getter 的名称(带命名空间)
 * @param {*} rawGetter getter
 * @param {*} local 绑定命名空间的上下文对象
 */
function registerGetter (store, type, rawGetter, local) {
  // ...
  store._wrappedGetters[type] = function wrappedGetter (store) {
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
  // ...
}

/**
 * 注册 mutations
 * @param {*} store store 实例
 * @param {*} type mutation 的名称(带命名空间)
 * @param {*} handler mutation 回调函数
 * @param {*} local 绑定命名空间的上下文对象
 */
function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler (payload) {
    handler.call(store, local.state, payload)
  })
}

/**
 * 注册 actions
 * @param {*} store store 实例
 * @param {*} type action 的名称(带命名空间)
 * @param {*} handler action 回调函数
 * @param {*} local 绑定命名空间的上下文对象
 */
function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload, cb) {
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload, cb)
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

rawAction.call(store, {
    dispatch: local.dispatch,
    commit: local.commit,
    getters: local.getters,
    state: local.state,
    rootGetters: store.getters,
    rootState: store.state
}, payload, cb)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

模块的局部 state/getters 为什么要实时获取?

由上一节上下文对象context实时获取state/getters,我们不禁怀有疑问,为什么要实时获取呢,难道局部state/getters会发生变化?

答案是,局部state/getters确实不是一直保持不变的。比如在以下情况下,stategetters是会发生变化的:

  • 通过store.registerModule注册动态模块时,会增加部分state/getter
  • 通过store.unregisterModule卸载动态模块时,会删除部分state/getter
  • 通过store.replaceState(常见的使用场景是 SSR)替换掉整个state

因此,state的动态变化显而易见。而getters的动态变化就略显繁琐一些。由store.state/getters 为什么是响应式的?一节我们可以看到,store.getters作为store._vm的计算属性,getters在上述几种情况下导致store._vm更换时,也会随之更换为新的对象。

调用 getter 时可以传入额外参数

若是项目里有需求,需要在调用getter时传入参数,那么可以如下定义getter,让getter的回调函数返回另一函数。

// store.js
new Vuex.Store({
  state: {
    a: 1
  },
  getters: {
    // 重点!! 此处的 aWithB 是一函数,该函数返回另一函数
    aWithB: (state) => (b) => {
      return state.a + b
    }
  },
  mutations: {
    SET_A: (state, payload) => {
      state.a = payload
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
  <div id="app">
    <div>a: {{ a }}</div>
    <div>aWithB: {{ aWithB(2) }}</div>
  </div>
</template>

<script>
import { mapState, mapGetters, mapMutations } from 'vuex'
export default {
  name: 'App',
  mounted () {
  },
  computed: {
    ...mapGetters([
      'aWithB'
    ]),
    ...mapState([
      'a'
    ])
  },
  created () {
    setTimeout(() => {
      this.SET_A(2)
    }, 2000)
  },
  methods: {
    ...mapMutations([
      'SET_A'
    ])
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

页面显示结果为:

// 初始时
a: 1
aWithB: 3

// 2000ms 后
a: 2
aWithB: 4
1
2
3
4
5
6
7

可以发现,名为aWithBgetter结果是state.a和调用aWithB时传入的参数b的和,而且在state.a改变时,其值也会随之改变。

TODO: 待重读最新的 Vue 响应式源码之后,再来详细分析这里一层层的依赖订阅关系。

在带命名空间的模块注册全局 action

{
  actions: {
    someOtherAction ({dispatch}) {
      dispatch('someAction')
    }
  },
  modules: {
    foo: {
      namespaced: true,

      actions: {
        someAction: {
          root: true,
          handler (namespacedContext, payload) { ... } // -> 'someAction'
        }
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

源码里是这么实现的:

function installModule (store, rootState, path, module, hot) {
  // ...

  // 遍历 action,并注册
  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })

  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12

Reference: 在带命名空间的模块注册全局 actionopen in new window

action 回调函数的结果,会处理成 Promise 对象

我们知道,action 回调函数可以返回 Promise 实例,比如:

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}
1
2
3
4
5
6
7
8
9
10

但是如果 action 回调函数没有返回 Promise 实例,Vuex 里会将返回结果处理成 Promise。

// store.js
export default new Vuex.Store({
  state: {
    a: 1
  },
  mutations: {
    SET_A: (state, payload) => {
      state.a = payload
    }
  },
  actions: {
    asynSetA: ({ commit }, payload) => {
      setTimeout(() => {
        commit('SET_A', payload)
      })
      return 'this is promise data'
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// App.vue
import { mapActions } from 'vuex'
export default {
  name: 'App',
  created () {
    this.asynSetA(2).then(res => {
      console.log('action 返回结果:', res)
    })
  },
  methods: {
    ...mapActions([
      'asynSetA'
    ])
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

结果会打印出action 返回结果: this is promise data

我们来学习下源码里是如何实现的。

/**
 * 注册 actions
 * @param {*} store store 实例
 * @param {*} type action 的名称(带命名空间)
 * @param {*} handler action 回调函数
 * @param {*} local 绑定命名空间的上下文对象
 */
function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload, cb) {
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload, cb)
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

源码里,会对模块上的action即上面的handler做一层封装,即wrappedActionHandlerwrappedActionHandler执行时,会先执行原始的handler,会根据其返回结果判断,若结果是 Promise 实例则直接返回,否则处理成 Promise 实例,将其状态置为resovled并返回结果。

在“命名空间的副作用”一节里我们知道,可能存在同名的多个action,而当分发的action存在多个时,需要这几个actionresolve了,最终返回的 Promise 才会改变状态为resolve

export class Store {
  /**
   * 分发 action
   * @param {*} _type action 的名称(带命名空间)
   * @param {*} _payload payload
   */
  dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }

    // action 执行前,先调用订阅 action 变化的回调函数
    this._actionSubscribers.forEach(sub => sub(action, this.state))

    // 若 action 有多个回调,都执行完了才算 resolve
    return entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

严格模式

严格模式下,无论何时发生了状态变更且不是由mutation函数引起的,将会抛出错误。

function enableStrictMode (store) {
  store._vm.$watch(function () { return this._data.$state }, () => {
    if (process.env.NODE_ENV !== 'production') {
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deep: true, sync: true })
}
1
2
3
4
5
6
7

开启严格模式,将深度监听state对象,若是发现修改statestore._committing的值为false,则此次修改state不是由mutation函数引起的,进而抛错。

而当执行mutation函数修改state对象时,会先将store._committing置为true,修改完之后,再将store._committing还原,详见_withCommit函数。

export class Store {
  // ...
  commit (_type, _payload, _options) {
    // check object-style commit
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    const entry = this._mutations[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    this._subscribers.forEach(sub => sub(mutation, this.state))

    if (
      process.env.NODE_ENV !== 'production' &&
      options && options.silent
    ) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
        'Use the filter functionality in the vue-devtools'
      )
    }
  }
  // ...
  _withCommit (fn) {
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing
  }
  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

命名空间的副作用

命名空间的实现机制,导致命名空间存在副作用——可能存在多个同名的action/mutation

因而在源码实现里,action/mutationkey对应的value不是函数,而是函数数组,在dispatch/commit时,会遍历调用action/mutation

function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler (payload) {
    handler.call(store, local.state, payload)
  })
}

function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload, cb) {
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload, cb)
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

而命名空间的副作用是如何产生的呢?我们先来看看 Vuex 是如何处理命名空间的。

// src/module/module-collection.js
class ModuleCollection {
  // ...
  getNamespace (path) {
    let module = this.root
    return path.reduce((namespace, key) => {
      module = module.getChild(key)
      return namespace + (module.namespaced ? key + '/' : '')
    }, '')
  }
  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12

如此可见,命名空间是有模块路径刷选而来的,针对模块链上的模块,如果该模块的namespacedtrue则该模块的名称就保留在最终的命名空间里。

因此,针对模块路径为firstsecond/first,假设second是不带命名空间的模块,first是带命名空间的模块,那么如果first模块和second模块的子模块first存在同名的mutation比如SET_Xaction同理),那么将导致在store.commit('first/SET_X')时,将同时提交这两个模块的SET_X提交。如下代码验证了以上的说法。

new Vuex.Store({
  state: {
    a: 1
  },
  modules: {
    first: {
      namespaced: true,
      state: {
        a: '2'
      },
      mutations: {
        SET_A: (state, payload) => {
          console.log('SET_A in first with namespace!')
          state.a = payload
        }
      }
    },
    second: {
      namespaced: false,
      state: {
        a: '2'
      },
      modules: {
        first: {
          namespaced: true,
          state: {
            a: 3
          },
          mutations: {
            SET_A: (state, payload) => {
              console.log('SET_A in second(without namespace) and it\'s child first with namespace!')
              state.a = payload
            }
          }
        }
      }
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Vue 组件实例里
// import { mapMutations } from 'vuex'
export default {
  name: 'App',
  mounted () {
    this.$store.commit('first/SET_A', 'the same value')
    // this.SET_A('the same value')

  },
  // methods: {
  //   ...mapMutations('first', [
  //     'SET_A'
  //   ])
  // }
}

// 输出为:
// SET_A in first with namespace!
// SET_A in second(without namespace) and it's child first with namespace!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

createNamespacedHelpers、mapState/mapMutation/mapAction

Vuex 里有个helpers.js模块,里面定义了createNamespacedHelpersmapStatemapMutationmapAction等辅助函数。

没读源码之前,一直好奇在这些辅助函数是如何获取到store实例的(但是自己却没有认真思想过这个问题..),读了源码才发现,原来都是从 Vue 实例上通过this.$store获取的。

整个辅助函数这块代码相当简单,此处不再赘述。

最佳实践

模块的 mutation/action 里不要用 this

实现里,根模块及嵌套子模块里的mutation/action里的this都是指向store实例,因此为了避免引起歧义,最好不要在mutation/action使用this

/**
 * 注册 mutations
 * @param {*} store store 实例
 * @param {*} type mutation 的名称(带命名空间)
 * @param {*} handler mutation 回调函数
 * @param {*} local 绑定命名空间的上下文对象
 */
function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler (payload) {
    // handler(模块里注册的 mutation 回调函数)调用时,this 指向 store 实例
    handler.call(store, local.state, payload)
  })
}

/**
 * 注册 actions
 * @param {*} store store 实例
 * @param {*} type action 的名称(带命名空间)
 * @param {*} handler action 回调函数
 * @param {*} local 绑定命名空间的上下文对象
 */
function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload, cb) {
    // handler(模块里注册的 action 回调函数)调用时,this 指向 store 实例
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload, cb)
    // ...
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37