SoFunction
Updated on 2025-04-10

Definition of multiplexed objects in vue

Definition of multiplexed objects

I have several components that I need to use, and the structures of these components are extremely similar. I am wondering whether I can reuse the object definition of the store? Because each component is defined once, it is cumbersome and unnecessary.

Mainly use cloning components. The specific code is as follows:

1. Shared store definition

/src/store/

export default {
  namespaced: true,
  state: {
    v: "hello store", // for test
    items: [],
  },
  getters: {
    v: (state) => ,
    items: (state) => {
      return ;
    },
  },
  mutations: {
    // Synchronous operation    setV(state, val) {
       = val;
    },
    setItems(state, val) {
       = val;
    },
  },
  actions: {
    keepItems: ({ commit }, val) => {
      commit("setItems", val);
    },
  },
};

2. Component 1

/src/store/

import cloneDeep from "lodash/cloneDeep";
import common from "./common";
const category = cloneDeep(common);
export default component1;

3. Component 2

/src/store/

import cloneDeep from "lodash/cloneDeep";
import common from "./common";
const category = cloneDeep(common);
export default component2;

store in vuex

1. What is Vuex

Before we learn about the Store, let’s take a look at what Vuex is. Vuex is essentially a plug-in, which is used to manage states for complex applications. After printing Vuex, output:

{
 
​ Store: function Store(){},
 
​ mapActions: function(){}, // The result set corresponding to Actions 
​ mapGetters: function(){}, // The result set corresponding to Getters 
​ mapMutations: function(){}, // The result set corresponding to Mutations 
​ mapState: function(){}, // The result set corresponding to State 
​ install: function install(){},
 
​ installed: true
 
}

Vuex and a simple global object have the following two differences:

  • Vuex's state storage is responsive. When a Vue component reads the state from the Store, if the state in the Store changes, the corresponding components will be updated efficiently accordingly.
  • The status in the Store cannot be changed directly. The only way to change the state in the Store is to explicitly submit the mutation.

2. Store

The core of every Vuex application is the Store (repository). We can say that the Store is a container. The state in the Store is different from the simple global variables, and the state in the Store cannot be directly changed. If you want to change the state in the Store, there is only one way to explicitly submit mutation.

3. A complete Store structure

const store = new ({
  state: {
    // Storage status  },
  getters: {
    // Computation properties of state  },
  mutations: {
    // Change the logic of the state in the state and synchronize the operation  },
  actions: {
    // Submit mutation, asynchronous operation  },
  // If you divide the store into modules, you need to use modules.   //Then write state, getters, mutations, actions, etc. in each module.  modules: {
    a: moduleA,
    b: moduleB,
    // ...
  }
});

4. Several core concepts of state management

1. state

state is state data, which can be obtained directly through this.$, or the mapState helper function provided by vuex can be used to map state into computed attributes (computed). The value received with data cannot respond and update in time, just use computed:

export default {
  data () {
    return {
      dataCount: this.$ //Receive with data    }
  },
  computed:{
    count(){
      return this.$ //Receive with computed    }
  }
}

mapState helper function:

mapState is the syntax sugar of state. When a component needs to obtain multiple states, declaring these states as computed properties will be somewhat duplicated and redundant. To solve this problem, we can use the mapState helper function to help us generate computed properties, allowing you to press the key a few times less:

// In a separate build version, the helper function isimport { mapState } from 'vuex'
 
export default {
  // ...
  computed: mapState({
    // Arrow function can make the code simpler    count: state => ,
 
    // Pass the string parameter 'count' is equivalent to `state => `    countAlias: 'count',
 
    // In order to be able to use `this` to get local state, regular functions must be used    countPlusLocalState (state) {
      return  + 
    }
  })
}

When the name of the mapped computed attribute is the same as the child node name of state, we can also pass an array of strings to mapState.

computed: mapState([
  // Map as  'count'
])

     

2. getter

getters are essentially used to process states. The relationship between Getters and State is like the relationship between computed and data. The return value of getter is cached according to its dependencies and will be recalculated only if its dependencies change. The derived state can be accessed through this.$. Or use the helper function mapGetters to map it to the local computed properties directly.

mapGetters helper function:

The mapGetters helper function simply maps getters in the store to local computed properties:

import { mapGetters } from 'vuex'
export default {
  // ...
  computed: {
  // Use the object expansion operator to mix getter into the computed object    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

mapGetters is actually a method on a method Vuex object, which can be seen from the content of the Vuex object printed at the beginning of this article. ...This symbol is a new syntax sugar in ES2015. It is to expand the content processed by mapGetters and fill in it.

If you want to give another name to a getter property, use the object form:

mapGetters({
  // Map `` for ``  doneCount: 'doneTodosCount'
})

       

3. mutation

Mutations means "change" in Chinese, and you can change the state using it. In fact, the only way to change the state in Vuex's store is to commit mutation. However, the way mutation triggers state changes is a bit special. The so-called commit mutation is actually like triggering an event, passing in the type of a mutation and carrying some data (called payload, payload).

mutations: {   //Put mutations method    increment(state, payload) {
        //Change the data in the state here         = ;
    }
},

So how do you represent a commit mutation at the code level?

this.$('increment', {
  amount: 10
})
// Orthis.$({
  type: 'increment',
  amount: 10
})

In addition to this way of submitting mutations using this.$('xxx') , there is another way to map methods in the component to this.$ using the mapMutations helper function. For example:

import { mapMutations } from 'vuex'
 
export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // Map `()` to `this.$('increment')` 
      // `mapMutations` also supports payloads:      'incrementBy' // Map `(amount)` to `this.$('incrementBy', amount)`    ]),
    ...mapMutations({
      add: 'increment' // Map `()` to `this.$('increment')`    })
  }
}

After such a mapping, the corresponding (map to which) mutation commit can be triggered by calling the method. For example, calling the add() method in the above example is equivalent to executing this.$('increment').

Considering that the type of the triggered mutation must be consistent with the mutation name declared in mutations, the better way is to concentrate these mutations into a file (such as mutation-types) and define them in the form of a constant, and introduce this file elsewhere for easy management. And there is another advantage in doing this, which is what mutation types are available in the entire application. Just like this:

// 
export const SOME_MUTATION = 'SOME_MUTATION'
 
// 
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
 
const store = new ({
  state: { ... },
  mutations: {
    // We can use the ES2015-style computed attribute naming function to use a constant as the function name    [SOME_MUTATION] (state) {
      // mutate state
    }
  }
})

         

4. action

The action can submit mutation, which can be executed in the action, and there can be any asynchronous operations in the action:

const store = new ({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      ++
    }
  },
  actions: {
    increment (context) {
      ('increment')
    }
  }
})

Or use the parameters of ES2015 to deconstruct, which can be abbreviated as:

actions: {
    increment ({commit}) {
          commit('increment')
    }
}

Similar to mutation, we handle life action functions like the above. The first parameter it receives is a context object with the same method and properties as the store instance, so you can call submit a mutation, or get state and getters via and .

However, what is done in the mutation handler is to change the state, while what is done in the action handler is to commit mutation.

How to trigger an action? According to Vuex, this is called distribution. Anyway, we just need to know that it actually means triggering. The specific trigger method is this.$(actionType, payload). The two parameters passed are one of the type of action to be triggered, and the other is the data carried (payload), similar to the two parameters passed during commit mutation mentioned above. The details are as follows:

// Distribute in load formthis.$('incrementAsync', {
  amount: 10
})

or

// Distribute in object formthis.$({
  type: 'incrementAsync',
  amount: 10
})

Another way is to use the mapActions helper function to map the components' methods to this.$ call. as follows:

import { mapActions } from 'vuex'
 
export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // Map `()` to `this.$('increment')` 
      // `mapActions` also supports payloads:      'incrementBy' // Map `(amount)` to `this.$('incrementBy', amount)`    ]),
    ...mapActions({
      add: 'increment' // Map `()` to `this.$('increment')`    })
  }
}

In addition, it should be noted that this.$ can handle the promise returned by the triggered action handler, and this.$ still returns the promise.

Let’s look at some combination of asynchronous operations:

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

Now you can:

$('actionA').then(() => {
  // ...
})

In another action, it is also possible:

actions: {
  // ...
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}

Finally, if we take advantage of the upcoming new JavaScript feature async/await, we can combine action like this:

// Assume that getData() and getOtherData() return Promiseactions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // Wait for actionA to complete    commit('gotOtherData', await getOtherData())
  }
}

Let's look at a more practical shopping cart example, involving calling an asynchronous API and distributing multiple mutations:

actions: {
  checkout ({ commit, state }, products) {
    // Back up items in the current shopping cart    const savedCartItems = [...]
    // Send a checkout request and then optimistically clear the cart    commit(types.CHECKOUT_REQUEST)
    // The shopping API accepts a successful callback and a failed callback    (
      products,
      // Successful operation      () => commit(types.CHECKOUT_SUCCESS),
      // Failed operation      () => commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
  }
}

         

5. module

module is a kind of cutting for the store. Since Vuex uses a single state tree, all states of the entire application will be concentrated on a relatively large object, then when the application becomes very complex, the store object may become quite bloated! It solves the problem that when the state is very bloated, the module can split the store into modules, each module has its own state, mutation, action and getter. Just like this:

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}
 
const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}
 
const store = new ({
  modules: {
    a: moduleA,
    b: moduleB
  }
})
 
 // -> moduleA status // -> moduleB The status of

Local state of the module

For mutations and getters inside each module, the first parameter received is the local state object of the module.

const moduleA = {
  state: { count: 0 },
  mutations: {
    increment (state) {
      // The `state` object here is the local state of the module      ++
    }
  },
 
  getters: {
    doubleCount (state) {
      return  * 2
    }
  }
}

Similarly, for actions inside the module, the local state is exposed through , and the root node state is:

const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if (( + ) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

For getters inside the module, the root node status will be exposed as the third parameter:

const moduleA = {
  // ...
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return  + 
    }
  }
}

Namespace

By default, actions, mutations, and getters inside modules are registered in the global namespace—this allows multiple modules to respond to the same mutation or action.

If you want your module to have higher encapsulation and reusability, you can make it a namespace module by adding namespaced: true. When a module is registered, all its getters, actions and mutations will be automatically adjusted and named according to the path registered by the module.

For example:

const store = new ({
  modules: {
    account: {
      namespaced: true,
 
      // module assets      state: { ... }, // The states in the module are already nested, using the `namespaced` property will not affect it      getters: {
        isAdmin () { ... } // -> getters['account/isAdmin']
      },
      actions: {
        login () { ... } // -> dispatch('account/login')
      },
      mutations: {
        login () { ... } // -> commit('account/login')
      },
 
      // Nested modules      modules: {
        // Inherit the namespace of the parent module        myPage: {
          state: { ... },
          getters: {
            profile () { ... } // -> getters['account/profile']
          }
        },
 
        // Further nesting namespaces        posts: {
          namespaced: true,
 
          state: { ... },
          getters: {
            popular () { ... } // -> getters['account/posts/popular']
          }
        }
      }
    }
  }
})

Getters and actions with namespace enabled will receive localized getters, dispatch, and commit.

Access global content within namespace module (Global Assets)

If you want to use global state and getters, rootState and rootGetter will be passed into the getter as the third and fourth parameters, and action will also be passed through the properties of the context object.

If you need to distribute action or submit mutation in the global namespace, just pass { root: true } as the third parameter to dispatch or commit.

modules: {
  foo: {
    namespaced: true,
 
    getters: {
      // In this module's getters, `getters` is localized      // You can use the fourth parameter of getter to call `rootGetters`      someGetter (state, getters, rootState, rootGetters) {
         // -> 'foo/someOtherGetter'
         // -> 'someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },
 
    actions: {
      // In this module, dispatch and commit are also localized      // They can accept the `root` property to access the root dispatch or commit      someAction ({ dispatch, commit, getters, rootGetters }) {
         // -> 'foo/someGetter'
         // -> 'someGetter'
 
        dispatch('someOtherAction') // -> 'foo/someOtherAction'
        dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
 
        commit('someMutation') // -> 'foo/someMutation'
        commit('someMutation', null, { root: true }) // -> 'someMutation'
      },
      someOtherAction (ctx, payload) { ... }
    }
  }
}

Binding function with namespace

When using mapState, mapGetters, mapActions and mapMutations functions to bind namespace modules, it may be cumbersome to write:

computed: {
  ...mapState({
    a: state => ,
    b: state => 
  })
},
methods: {
  ...mapActions([
    'some/nested/module/foo',
    'some/nested/module/bar'
  ])
}

For this case, you can pass the module's space name string as the first parameter to the above function, so that all bindings automatically take the module as context. So the above example can be simplified to:

computed: {
  ...mapState('some/nested/module', {
    a: state => ,
    b: state => 
  })
},
methods: {
  ...mapActions('some/nested/module', [
    'foo',
    'bar'
  ])
}

Moreover, you can create a namespace helper function based on a certain namespace by using createNamespacedHelpers. It returns an object with a new component binding helper function bound to the given namespace value:

import { createNamespacedHelpers } from 'vuex'
 
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
 
export default {
  computed: {
    // Find in `some/nested/module`    ...mapState({
      a: state => ,
      b: state => 
    })
  },
  methods: {
    // Find in `some/nested/module`    ...mapActions([
      'foo',
      'bar'
    ])
  }
}

Things to note for plugin developers

If the plugin you developed provides modules and allows users to add them to the Vuex store, you may want to consider the module's space name issue. For this case, you can allow the user to specify the space name through the plugin's parameter object:

// Get the space name through the parameter object of the plugin// Then return the Vuex plugin functionexport function createPlugin (options = {}) {
  return function (store) {
    // Add the space name to the plug-in module type (type)    const namespace =  || ''
    (namespace + 'pluginAction')
  }
}

Module dynamic registration

After the store is created, you can register the module using the method:

// Register module `myModule`('myModule', {
  // ...
})
// Register nested module `nested/myModule`(['nested', 'myModule'], {
  // ...
})

The module's status can then be accessed through and through.

The module dynamic registration feature allows other Vue plug-ins to use Vuex to manage state by attaching new modules to the store. For example, the vuex-router-sync plug-in combines vue-router and vuex through the dynamic registration module to realize the routing state management of the application.

You can also use (moduleName) to dynamically uninstall modules. Note that you cannot uninstall static modules (i.e. modules declared when the store is created).

Module reuse

Sometimes we may need to create multiple instances of a module, for example:

(1) Create multiple stores, they share the same module

(2) Register the same module multiple times in a store

If we use a pure object to declare the state of a module, then this state object will be shared through references, resulting in the problem of store or data between modules contamination when the state object is modified.

In fact, this is the same problem as data inside the Vue component. So the solution is the same - use a function to declare module state (only supported by 2.3.0+):

const MyReusableModule = {
  state () {
    return {
      foo: 'bar'
    }
  },
  // mutation, action and getter etc...}

5. The difference between store and $store

$store is mounted on a Vue instance (i.e.), and the component is also a Vue instance. In the component, you can use this to access properties on the prototype. You can directly access it through {{ $ }} in template, which is equivalent to this.$ in script.

As for {{ }}, the data in script must be declared in the store before it can be accessed.

In short, there are the following things to note:

(1) In terms of function:

  • stateThe data is saved
  • gettersIt is to perform secondary processing of state
  • actionThe function of the handler function is ultimately commit mutation
  • mutationThe function of the processing function is ultimately changing the state

(2) In the process:

vue component—-dispatch—->actions—-commit—->mutations—-mutate—->state—-render—->vue component. This creates a closed loop.

(3) Mapping of auxiliary methods:

mapGetters and mapState are used in computed statements;

mapActions and mapMutations are used in methods declarations.

The above is personal experience. I hope you can give you a reference and I hope you can support me more.