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:
-
state
The data is saved -
getters
It is to perform secondary processing of state -
action
The function of the handler function is ultimately commit mutation -
mutation
The 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.