background

In the recent requirement development process, Vuex was used again. In terms of status update, I always followed the official “instructions”, keeping in mind that “State must not be changed in action, but should be changed in mutation”. So I can’t help but wonder: why does Vuex make this restriction, and on what basis? With this question, I check the source code of Vuex. Now please follow my steps to uncover the veil of this problem.

Read the source code together

SRC /store.js; SRC /store.js; SRC /store.js

// ...
this.dispatch = function boundDispatch (type, payload) {
  return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
  return commit.call(store, type, payload, options)
}
// ...
Copy the code

Here are Vuex’s two core apis: Dispatch and commit are used to commit actions and mutations, respectively. The purpose of this article is to “understand why you cannot change state in an action”. However, mutation is committed via COMMIT, so let’s first look at the internal implementation of COMMIT

commit&mutation

The core code of 2.com MIT method is roughly as follows:

commit (_type, _payload, _options) {
    // ...
    this._withCommit((a)= > {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    // ...
}
Copy the code

Vuex uses _withCommit as a parameter to wrap mutations of a particular type around _withCommit. So let’s look at the internal implementation of _withCommit. Vuex does support declaring multiple mutations of the same name, but only if they are under different namespaces. The action in the same way)

The 3._withCommit method looks like this:

_withCommit (fn) {
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing
  }
Copy the code

Yes, you read that right, it’s really only four lines of code; What we notice here is a flag bit (_research), this flag bit will be set to true before we do fn (fn), which we’ll remember and use later)

4. Next, I’ll introduce you to the resetStoreVM function, which initializes the store. It is first executed in the store constructor

function resetStoreVM (store, state, hot) {
  // ...
  if (store.strict) {
    enableStrictMode(store)
  }
// ...
}
Copy the code

There is one thing to note here: resetStoreVM makes strict checks. If strict is enabled, then enableStrictMode is executed. Let’s move on to its internal implementation

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})}Copy the code

This listens for the state of the Vue component instance, and when it does, it executes an asset (assertion), which happens to be the _research flag BIT I told you to remember (right), so what does this asset do

The asset method is in the SRC /util.js file

export function assert (condition, msg) {
  if(! condition)throw new Error(`[vuex] ${msg}`)}Copy the code

The method is as simple as determining whether the first argument is truly and throwing an exception if it is not

Now that we’ve looked briefly at the logic of COMMIT and mutation, let’s take a look at Dispatch and Action

dispatch&action

6. The dispatch code is roughly as follows:

dispatch (_type, _payload) {
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]

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

Note here that when an action of a type has only one declaration, the action’s callbacks are executed as normal functions, whereas when there are multiple declarations, they are treated as Promise instances and executed with promise.all. Promise. All does not guarantee the order when executing a Promise, that is, if there are 3 Promise instances: P1, P2, P3, which may not return a result first, then we should consider: What happens if you change the same state in multiple actions at the same time?

In fact, we can simply change the same state in multiple actions, because it is possible that each action will assign a different value to state, and there is no guarantee that the last action will return a result, so the final value of state may be wrong

So why does Vuex use promise.all to perform the action? It is also for performance reasons, so that we can maximize the concurrency of asynchronous operations

What eagle-eyed students might notice is that you don’t see _research () in dispatch (Vuex), which limits action state (right) : When the action wants to change state, the asset stage doesn’t pass because _research isn’t set to true beforehand

This limitation is limited to the development phase, however, because in the enableStrictMode function, Webpack adds a judgment about the environment and will only print the asset (assertion) line if it is not the production (that is, the development) environment

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})}Copy the code

So if you force action to change state in production, Vuex won’t stop you. It might just warn you. In this case, Vuex did not use promise. all to perform the action, and the result of mutation would be the same if we could guarantee only one declaration for an action of the same type. So there is no question of the order of return results

dispatch (_type, _payload) {
    // ...
    const result = entry.length > 1
      ? Promise.all(entry.map(handler= > handler(payload)))
      : entry[0](payload)
    // ...
  }
Copy the code

However, all the conventions that depend on human observance are unreliable, so when we use Vuex in daily life, we’d better abide by the official restrictions, otherwise there may be bugs in the online code, which is not what we expect.

conclusion

Vuex’s restriction is also a code design consideration. Action and Mutation operate separately, essentially following the “single responsibility” principle. Vuex does not allow you to modify the state in action

Pay attention to our