# Actions 相关

# 构造器选项之一

actionsVuex.Store 构造器选项之一,需要用户设置,官方 API 说明如下:

actions

  类型: { [type: string]: Function }
  
  在 store 上注册 action。处理函数总是接受 context 作为第一个参数,payload 作为第二个参数(可选)。
1
2
3
4
5

context 对象包含以下属性:

{
  state,      // 等同于 `store.state`,若在模块中则为局部状态
  rootState,  // 等同于 `store.state`,只存在于模块中
  commit,     // 等同于 `store.commit`
  dispatch,   // 等同于 `store.dispatch`
  getters,    // 等同于 `store.getters`
  rootGetters // 等同于 `store.getters`,只存在于模块中
}
1
2
3
4
5
6
7
8

来看看下面这个例子:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment') // context 对象
    }
    // 或者
    increment ({ commit }) {
        commit('increment')  // 使用ES 参数结构 context 对象
    }
    // 组合Action
    /* 一个 store.dispatch 在不同模块中可以触发多个 action 函数。
     在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。*/
    async actionA ({ commit }) {
      commit('gotData', await getData())
    },
    async actionB ({ dispatch, commit }) {
      await dispatch('actionA') // 等待 actionA 完成
      commit('gotOtherData', await getOtherData())
    }
  }
})

// 分发
// 以载荷形式分发
store.dispatch('incrementAsync', {
  amount: 10
})

// 以对象形式分发
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})
// 组件中分发
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')`
    })
}
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

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation, 或者通过 context.statecontext.getters 来获取 state 和 getters。当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了。

# 实例方法

# dispatch

dispatchVuex.Store 的实例方法,官方 API 说明如下:

dispatch(type: string, payload?: any, options?: Object)

dispatch(action: Object, options?: Object)


分发 action。options 里可以有 root: true,它允许在命名空间模块里分发根的 action。
返回一个解析所有被触发的 action 处理器的 Promise。
1
2
3
4
5
6
7

dispatch 实现逻辑:

/* src/store.js */

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
    }

    try {
      this._actionSubscribers
        .filter(sub => sub.before)
        .forEach(sub => sub.before(action, this.state))
    } catch (e) {
      if (process.env.NODE_ENV !== 'production') {
        console.warn(`[vuex] error in before action subscribers: `)
        console.error(e)
      }
    }
    /**
    * 因为 dispatch 支持异步,所以这里作者使用 Promise.all 来执行异步函数
    * 并且判断所有异步函数是否都已经执行完成,
    * 所以在文件最开始判断了当前环境必须支持 promise 就是这个原因
    */
    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)

    return result.then(res => {
      try {
        this._actionSubscribers
          .filter(sub => sub.after)
          .forEach(sub => sub.after(action, this.state))
      } catch (e) {
        if (process.env.NODE_ENV !== 'production') {
          console.warn(`[vuex] error in after action subscribers: `)
          console.error(e)
        }
      }
      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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

store.dispatch 可以处理被触发的 actionaction 的处理函数返回的 Promise,并且store.dispatch 仍旧返回 Promise。一个store.dispatch在不同模块中可以触发多个 action 函数。 在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。根据 type 找到 actions,然后使用 Promise 异步的调用回调函数。这也是为什么 dispatch 可以异步执行的原因。

# subscribeAction

subscribeActionVuex.Store 的实例方法,官方 API 说明如下:

subscribeAction(handler: Function): Function 
订阅 store 的 action
1
2

handler 会在每个 action 分发的时候调用并接收 action 描述当前的 store 的 state 这两个参数:

// 2.5.0 新增
store.subscribeAction((action, state) => {
  console.log(action.type)
  console.log(action.payload)
})
1
2
3
4
5

停止订阅,调用此方法返回的函数即可停止订阅。

从 3.1.0 起,subscribeAction 也可以指定订阅处理函数的被调用时机应该在一个 action 分发之前还是之后 (默认行为是之前):

// 3.1.0 新增
store.subscribeAction({
  before: (action, state) => {
    console.log(`before action ${action.type}`)
  },
  after: (action, state) => {
    console.log(`after action ${action.type}`)
  }
})
1
2
3
4
5
6
7
8
9

subscribeAction 的实现逻辑:

/* src/store.js */

subscribeAction (fn) {
    const subs = typeof fn === 'function' ? { before: fn } : fn
    return genericSubscribe(subs, this._actionSubscribers)
}

function genericSubscribe (fn, subs) {
  if (subs.indexOf(fn) < 0) {
    subs.push(fn)
  }
  return () => {
    const i = subs.indexOf(fn)
    if (i > -1) {
      subs.splice(i, 1)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

subscribe 一样,调用 subscribeAction 时会将回调函数保存_actionSubscribers 中,每次通过 dispatch 触发 action 时,会根据 _actionSubscribers 保存的回调函数依次触发。

# mapActions 辅助函数

mapActions 组件绑定的辅助函数,创建组件方法分发 action。官方 API 说明如下:

mapActions

  mapActions(namespace?: string, map: Array&lt;string> | Object&lt;string | function>): Object
  
  创建组件方法分发 action。
  
  第一个参数是可选的,可以是一个命名空间字符串。
  对象形式的第二个参数的成员可以是一个函数。function(dispatch: function, ...args: any[])
1
2
3
4
5
6
7
8

使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store)

/* src/helpers.js */

/**
 * Reduce the code which written in Vue.js for dispatch the action
 * @param {String} [namespace] - Module's namespace
 * @param {Object|Array} actions # Object's item can be a function which accept `dispatch` function 
 * as the first param, it can accept anthor params. You can dispatch action and do any other things 
 * in this function. specially, You need to pass anthor params from the mapped function.
 * @return {Object}
 */
export const mapActions = normalizeNamespace((namespace, actions) => {
  const res = {}
  if (process.env.NODE_ENV !== 'production' && !isValidMap(actions)) {
    console.error('[vuex] mapActions: mapper parameter must be either an Array or an Object')
  }
  normalizeMap(actions).forEach(({ key, val }) => {
    res[key] = function mappedAction (...args) {
      // get dispatch function from store
      let dispatch = this.$store.dispatch
      if (namespace) {
        const module = getModuleByNamespace(this.$store, 'mapActions', namespace)
        if (!module) {
          return
        }
        dispatch = module.context.dispatch
      }
      return typeof val === 'function'
        ? val.apply(this, [dispatch].concat(args))
        : dispatch.apply(this.$store, [val].concat(args))
    }
  })
  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
32
33

实现逻辑与 mapMutations 异曲同工,不在赘述。