preface

Before reading the Dispatch source code, I hope you can take a few minutes to review how it is used so you can quickly understand it.

The source code interpretation

Relevant methods

  • UnifyObjectStyle Checks the parameter type and adjusts it accordingly. Details (Hint: Scroll down after jump).

Dispatch source

dispatch (_type, _payload) {
    // Check the parameter type and adjust accordingly
    const {
      type, // Trigger type, e.g. Store.dispatch ('increment')
      payload // Commit the payload as a parameter, e.g. Store.dispatch ('increment', 10)
    } = unifyObjectStyle(_type, _payload)

    const action = {
      type,
      payload
    }
    // this._actions, the object defined by the store instance for storing actions
    const entry = this._actions[type]
    // Block entry if it does not exist
    if(! entry) {if (__DEV__) {
        console.error(`[vuex] unknown action type: ${type}`)}return
    }

    try {
      // This. _actionSubscribers stores the function passed in when store.subscribeAction is called
  
      // Slice is used for shallow replication to prevent iterator invalidation due to subscriber synchronization calls to unsubscribe.
      
      // filter filters the elements that meet the criteria (including the before attribute) into a new array and iterates through it
      // Elements in this array and call before. This filter is the same as the Store instance method: subscribeAction
      // About (more on that later).
      this._actionSubscribers
        .slice()
        .filter(sub= > sub.before)
        .forEach(sub= > sub.before(action, this.state))
    } catch (e) {
      if (__DEV__) {
        console.warn(`[vuex] error in before action subscribers: `)
        console.error(e)
      }
    }
    
    // The reason for using promise.all: a single store.dispatch can trigger multiple action functions in different modules.
    // In this case, the returned Promise will not be executed until all triggering functions have completed.
    const result = entry.length > 1 ?
      Promise.all(entry.map(handler= > handler(payload))) : entry[0](payload)

    return new Promise((resolve, reject) = > {
      result.then(res= > {
        try {
          // filter filters out the elements that meet the criteria (including the after attribute), forms a new array, and iterates through
          // Elements in this array and call after. This filter is related to the Store instance method: subscribeAction.
          this._actionSubscribers
            .filter(sub= > sub.after)
            .forEach(sub= > sub.after(action, this.state))
        } catch (e) {
          if (__DEV__) {
            console.warn(`[vuex] error in after action subscribers: `)
            console.error(e)
          }
        }
        resolve(res)
      }, error= > {
        try {
          // filter filters out the elements that meet the criteria (including the error attribute), forms a new array, and iterates through
          // Elements in this array and call error. This filter is related to the Store instance method: subscribeAction.
          this._actionSubscribers
            .filter(sub= > sub.error)
            .forEach(sub= > sub.error(action, this.state, error))
        } catch (e) {
          if (__DEV__) {
            console.warn(`[vuex] error in error action subscribers: `)
            console.error(e)
          }
        }
        reject(error)
      })
    })
}
Copy the code

To understand the operation on this._actionSubscribers in the source code, we need to look at the instance method of vuex.store: subscribeAction.

As we look at this method, we can see the definition of what it does:

It is used to subscribe to the Store action. Handler will call and receive the action description and the current store state at each action distribution

If you are not clear about this passage, you can read the official example several times:

store.subscribeAction((action, state) = > {
  console.log(action.type)
  console.log(action.payload)
})
Copy the code
  1. Handler refers to the function that is passed in to the subscribeAction method.
  2. I can receive the action description and the current store’s state because this._actionSubscribers are already given in when I’m iterated.

Words alone can not be too clear. So, let’s take a look at the case and its source code.

SubscribeAction case

The examples used are counter test cases provided in the VUex project (directory location: examples/counter).

Call the Store instance methods Dispatch and subscribeAction:

Parameter printing:

In the figure, not only did we print the parameter that was passed in, but also this._ActionSubscribers. It starts out as an empty array, and once you call Store. subscribeAction, that array is fed into a function that is the handler that gets called to subscribeAction.

SubscribeAction source

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

If fn is a function, an object is assigned to subs with the key before and fn as the value. If you’ve read the subscribeAction tutorial in Vuex carefully, you know why you do this:

As of 3.1.0, subscribeAction can also specify whether the subscription handler should be called before or after an action is distributed (the default behavior is before).

This is the corresponding section of the code, quoted from the official documentation. You can debug it yourself.

store.subscribeAction({
  before: (action, state) = > {
    console.log(`before action ${action.type}`)},after: (action, state) = > {
    console.log(`after action ${action.type}`)}})Copy the code

The genericSubscribe method was introduced in my last article and will not be repeated here. Also, the reason for calling slice on this._actionSubscribers is the same as the reason for calling slice on this._subscribers mentioned in the previous article.

A single store.dispatch can trigger multiple action functions in different modules

Remember this code? This is another important point in the Dispatch source code.

const result = entry.length > 1 ?
      Promise.all(entry.map(handler= > handler(payload))) : entry[0](payload)
Copy the code

As we went through vuex’s Actions tutorial, did you notice the last sentence: a single store.dispatch can trigger multiple action functions in different modules? In this case, the returned Promise will not be executed until all triggering functions have completed.

As you might expect, the corresponding implementation of this statement is the code above, where the key is the use of promise.all. To make the above statement clearer, let’s give an example.

Code section

In vuex directory location: examples/store/counter js, registered in the root module and moduleB module as the action: increment, in order to convenient, just stick out of the need to change the part, other part are the same.

// Both root and moduleB modules register the same INCREMENT
const actions = {
  increment: ({ commit }) = > commit('increment'),}const moduleB = {
  // namespaced: true,
  actions: {
    increment: ({ commit }) = > commit('increment')}}Copy the code

call

Located in vuex directory location: / counter examples/counter. Vue, directly in the mounted life cycle function calls, convenient testing.

mounted () {
    // Called here only for testing purposes
    this.$store.dispatch('increment')},Copy the code

Printing parameters

Located in the vuex directory: SRC /store.js, find the dispatch source section to print the parameters.

The results of

store.dispatch().then()

You know, call store. When Dispatch dispatches actions, you can use then, as shown in the code below.

store.dispatch('actionA').then(() = > {
  // ...
})
Copy the code

For the reason, if you’ve read the Dispatch source code carefully, you know that it finally returns a Promise object.

conclusion

I have been a little busy recently. After reading the COMMIT source code, I have been trying to read the Dispatch source code as well. Now I have finally made up for it. At the same time, if you see this article and find some mistakes in interpretation, please kindly comment, and we will make progress together and lose our hair.