Why Vuex

Generally, data communication in Vue project can be solved in the following three ways. However, with the increase of multi-layer nested components in the project, state transfer between sibling components is very tedious, which leads to constant changes of state through events and synchronization of multiple copies of state. Finally, the code is difficult to maintain. So Yudah developed Vuex to solve this problem.

  • The father the sonprops;
  • Child the parent$emit;
  • eventBusEvent bus.

Of course, small and medium Vue projects can not use Vuex. When the following two situations occur, we should consider using Vuex to manage state uniformly.

  • Multiple views depend on the same state;
  • Actions from different views need to change the same state.

The advantages of using Vuex are also obvious:

  • Convenient global communication;
  • Convenient state caching;
  • Convenient throughvue-devtoolsTo do state-related bug checking.

At the beginning of Vuex use

The official Vuex has an illustration explaining the Vuex, but there is no clear explanation. Here is a brief description of the function and role of each block, as well as the one-way data flow of the whole flow chart.

  • Vue Components: Vue Components. On the HTML page, responsible for receiving user operations and other interactive behaviors, execute the Dispatch method to trigger corresponding actions to respond.

  • Dispatch: Action action trigger method, the only method that can execute an action.

  • Actions: Action handling module. Responsible for handling all interactions received by Vue Components. Contains synchronous/asynchronous operations and supports multiple methods of the same name that are triggered in the order of registration. Requests to the background API are made in this module, including triggering other actions and submitting mutations. This module provides the encapsulation of promises to support the chain firing of actions.

  • Commit: State changes commit operation method. Committing for mutation is the only way to perform mutation.

  • Mutations open the door. Is the only recommended method for Vuex to modify state. Other modification methods will report errors in strict mode. This method can only perform synchronous operations, and the method name must be globally unique. Some hooks will be exposed during the operation for state monitoring and so on.

  • State: page state management container object. Centralized storage of scattered data objects in Vue Components, globally unique, for unified state management. The data required for page display is read from this object, leveraging Vue’s fine-grained data response mechanism for efficient status updates.

  • After receiving the interaction behavior, the Vue component calls the Dispatch method to trigger the relevant action processing. If the page state needs to be changed, it calls the commit method to submit the mutation and modify the state, and obtains the new value of state through getters. Re-render Vue Components, and the interface is updated.

Conclusion:

  1. State is stored in the state we mentioned above.

  2. Mutations is how to change the status.

  3. Getters derive state from state, such as filtering a state from state and retrieving a new state.

  4. Actions, an enhanced version of mutation, can change state using the method in Commit Mutations, and most importantly, it can operate asynchronously.

  5. Modules As the name suggests, when it’s still confusing to use the container to hold these states, we can split the container into chunks and separate the states and management rules into categories. This is the same purpose we used to create JS modules to make the code structure clearer.

Questions about Vuex

Vuex was used in our project, and some questions were left in the process of using Vuex, which could not be answered in terms of usage. So the question simple list, recently looked at the Vuex source code to understand.

Read the Vuex source repository

  • How to ensurestateChanges can only be made inmutationIn the callback function?
  • mutationsWhy can be modifiedstate?
  • Why can passthis.commitTo invoke themutationFunction?
  • actionsIn the functionThe context objectWhy notStore instanceItself?
  • Why is itThe actions functionCan be called indispatchorcommit?
  • throughthis.$store.getters.xxHow is it accessiblegetterIs the result of the execution of the function?

Vuex source code analysis

In view of the above doubts, in the process of looking at Vuex source code slowly solved the doubts.

1. How to guaranteestateChanges can only be made inmutationIn the callback function?

There is a _withCommit function in the Store class of Vuex source code:

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

All changes to state in Vuex call the _withCommit function wrapper, ensuring that this. __research is always true (case) while synchronizing state changes. When we detect a state change, if this._research doesn’t true (right), we know there is a problem with the state change.

2. The method in mutations, why can state be modified?

When Vuex is instantiated, Store is called, and Store calls installModule to register and install the module for the incoming configuration. Mutations are registered and installed, and the registerMutation method is called:

State * @param {*} store store instance * @param {*} type mutation key * @param {*} handler The mutation performed function * @param {*} local Specifies the current module */
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)
  })
}
Copy the code

Handler. Call (store, local.state, payload); Local. State is the state of the current module, and payload is an additional parameter.

Because we changed the this of the mutation function to point to a Store instance, we made it easy to change this.state.

3. Why can I passthis.commitTo invoke themutationFunction?

In Vuex, the mutation call is made via the STORE instance’s API interface COMMIT. Consider the definition of the commit function:

/** ** @param {*} _type mutation type * @param {*} _payload Additional parameters * @param {*} _options Some configurations */
  commit (_type, _payload, _options) {
    // check object-style commit
    // The unifyObjectStyle method handles the commit multiple forms of parameter passing
    // Commit the payload form and the underlying processing of the object form
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options) 

    const mutation = { type, payload }

    // search for mutation based on type
    const entry = this._mutations[type]
    // No error was detected
    if(! entry) {if(process.env.NODE_ENV ! = ='production') {
        console.error(`[vuex] unknown mutation type: ${type}`)}return
    }

    // mutation was committed using the this._withCommit method
    this._withCommit((a)= > {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })

    // Iterate over this._subscribers, calling the callback and passing in mutation and the current root state as arguments
    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')}}Copy the code

This._mutations [type], which receives the mutation type and external parameters, matches the corresponding mutation function in commmit’s implementation, and calls this._mutations[type].

4. In actionsThe context objectWhy not the Store instance itself?

5. WhyThe actions functionCan be called indispatchorcommit?

Use of actions:

actions: {
    getTree(context) {
        getDepTree().then(res= > {
            context.commit('updateTree', res.data)
        })
    }
}
Copy the code

In the action initialization function, there is this code:

@param {*} store Global store * @param {*} type Action type * @param {*} Handler Action function * @param {*} local Current module */
function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload) {

    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload)
    
    if(! isPromise(res)) { res =Promise.resolve(res)
    }
    // Store._devtoolHook is assigned when Store constructor is performed
    if (store._devtoolHook) {
      return res.catch(err= > {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

Copy the code

Context is specified, not store, const {dispatch, commit, getters, state, rootGetters,rootState} = context

The context object is mounted:

  • Dispatch, the dispatch function on the current module
  • Commit, the commit function on the current module
  • Getters, getters on the current module
  • State, the state of the current module
  • RootGetters, getters on the root module
  • RootState State of the root module

Through 6.this.$store.getters.xxHow can I access the results of the execution of getter functions?

In the Vuex source Store instance implementation there is a method called resetStoreVM:

function resetStoreVM (store, state, hot) {
    const oldVm = store._vm

    // bind store public getters
    store.getters = {}
    const wrappedGetters = store._wrappedGetters
    const computed = {}
    Object.keys(wrappedGetters).forEach(key= > {
        const fn = wrappedGetters[key]
        // use computed to leverage its lazy-caching mechanism
        computed[key] = (a)= > fn(store)
        Object.defineProperty(store.getters, key, {
        get: (a)= > store._vm[key]
        })
    })
    
    // ...
    
    store._vm = new Vue({
        data: { state },
        computed
    })
    
    // ...
}
Copy the code

Walk through the store._wrappedgetters object, get the wrapper function for each getter during the run, and store the result of the wrapper function’s execution using computed temporarily.

Then an instance of Vue is instantiated, passing in computed above as a calculated property and state in the state tree as data, completing the registration.

We can access this.$store.getters. Xxgetter in the component, which is equivalent to accessing store._vm[xxgetter], which is computed[xxgetter], and which accesses the xxgetter callback function.

reference

  • Flux Architecture primer
  • Vuex website