Consolidate the basic knowledge of Vue to fully understand Vuex, which is easier to understand than the official

Consolidate the basic knowledge of Vue to fully understand Vuex, which is easier to understand than the official (Part 1)

Vuex advanced operation

auxiliary function

mapState

As we said earlier, when the component accesses the value in the store instance, we can use computed to calculate the property. If we access a certain value, it is OK, but if we need to access multiple values, we need to write multiple calculated properties in computed. This is neither easy nor elegant. For such cases, Vuex has prepared auxiliary functions for us.

// In the separately built version, the auxiliary function is Vuex.mapState
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // Arrow function can make the code more concise
    count: state => state.count,

    // The string parameter 'count' is equivalent to ` state = > state.count`
    countAlias: 'count',

    // In order to be able to use 'this' to get the local state, you must use a regular function
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}
copy

When the name of the mapped calculated attribute is the same as the name of the child node of state, we can also pass a string array to mapState.

computed: mapState([
  // Map this Count is store.state.count
  'count'
])
copy

The mapState function returns an object. How do we mix it with locally computed properties? Usually, we need to use a tool function to merge multiple objects into one, so that we can pass the final object to the calculated attribute. But with the object expansion operator, we can greatly simplify the writing:

computed: {
  localComputed () { /* ... */ },
  // Use the object expansion operator to blend this object into an external object
  ...mapState({
    // ...
  })
}
copy

mapGetters

mapGetters are also placed in calculated, and the use method is similar to mapState

import { mapGetters } from 'vuex'

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

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

...mapGetters({
  // Put ` this Donecount is mapped to ` this.$store.getters.doneTodosCount '`
  doneCount: 'doneTodosCount'
})
copy

mapMutations

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // Put ` this Increment() ` map to ` this.$store.commit('increment ')`

      // `mapMutations ` also supports payload:
      'incrementBy' // Put ` this Incrementby (amount) ` map to ` this. $store commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // Put ` this Add() ` map to ` this.$store.commit('increment ')`
    })
  }
}
copy

mapActions

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // Put ` this Increment() ` map to ` this.$store.dispatch('increment ')`

      // `mapActions ` also supports loads:
      'incrementBy' // Put ` this Incrementby (amount) ` map to ` this. $store dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // Put ` this Add() ` map to ` this.$store.dispatch('increment ')`
    })
  }
}
copy

modules

When writing a Vue project, if a project is too large, we will split it into individual components. The same is true for Vuex. When too much content is stored in a store instance, it will become very bloated. At this time, we can also split it into individual components, similar to the following.

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const moduleA = {
  state: () => ({ count: 5 }),
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: { },
  getters: {
    name: () => { return 'moduleA' }
  }
}
const moduleB = {
  state: () => ({ count: 10 }),
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: { },
  getters: {
    name: () => { return 'moduleB' }
  }
}

export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    a: moduleA,
    b: moduleB
  }
})
copy

Local status of the module

For the mutation and getter inside the module, the first parameter received is the local state object of the module.

Similarly, for the action inside the module, you can use context State. To access the state of the root node, you can use context.rootState:

For getter s inside the module, the root node state will be passed in as the third parameter (the order cannot be wrong)

getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
copy

How do we access the state and mutation in the module? In module, state is the local state of the module, so we can access it in this way

this.$store.state.a.count // -> 5
copy

By default, the actions, mutations, and getter s inside the module are registered in the global namespace -- this enables multiple modules to respond to the same mutation or action. Take a chestnut

  mounted () {
    setInterval(() => {
      this.$store.commit('increment')
    }, 1000)
  }
copy

When we trigger a Mutation, the same name Mutation in the module will be triggered at the same time, and the state in both modules will change. The same is true for action, so I won't show it. As for getters, they will also be registered in the global namespace. If there are getters with the same name in two modules, the module introduced first will prevail.

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const moduleA = {
  state: () => ({ count: 5 }),
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: { },
  getters: {
    name: () => { return 'moduleA' }
  }
}
const moduleB = {
  state: () => ({ count: 10 }),
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: { },
  getters: {
    name: () => { return 'moduleB' }
  }
}

export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    a: moduleA,
    b: moduleB
  }
})
copy
this.$store.getters.name // -> 'moduleA'
copy

Namespace

So, what if we want to keep each module independent, not affect the global space, and maintain better encapsulation? Vuex provides us with the option to open the namespace. We only need to add named: true inside the module to open the namespace of the module.

After the namespace is opened, the getters and actions in the current module will receive localized getters, dispatch and commit, so our code does not need to be changed. However, when we call getters, actions and mutations in the module externally, that is, in the vue component, we need to add the module name. Since state is the local state in the module, it is the same whether or not to add the namespace

const store = new Vuex.Store({
  modules: {
    // Module name: account
    account: {
      namespaced: true,

      // module assets
      state: () => ({ ... }), // The states in the module are already nested. Using the 'named' attribute will not affect them 
      getters: {
        isAdmin () { ... } // -> this.$store.getters['account/isAdmin']
      },
      actions: {
        login () { ... } // -> this.$store.dispatch('account/login')
      },
      mutations: {
        login () { ... } // -> this.$store.commit('account/login')
      },

      // Nested modules
      modules: {
        // Inherit the namespace of the parent module
        myPage: {
          state: () => ({ ... }),
          getters: {
            profile () { ... } // -> this.$store.getters['account/profile']
          }
        },

        // Further nested namespaces
        posts: {
          namespaced: true,
          state: () => ({ ... }),
          getters: {
            popular () { ... } // -> this.$store.getters['account/posts/popular']
          }
        }
      }
    }
  }
})
copy

What if we open the namespace and want to access the global content inside the module?

In the getter, we can receive the third parameter rootState to access the global state and the fourth parameter rootGetters to access the global getter

// Inside the module
getters:{
  someGetter (state, getters, rootState, rootGetters) {
    ...
  },
}
copy

If we want to call the global action or Mutation in the internal action of the module, we only need to pass {root: true} as the third parameter to dispatch or commit when calling.

// Inside the module
actions: {
    someAction ({dispatch, commit}) {
        dispatch('someOtherAction', null, { root: true })
        commit('someMutation', null, { root: true })
    }
}
copy

Register global action in the module with namespace if you need to register global action in the module with namespace, you can add root: true and put the definition of this action in the function handler. Just as we use watch when we need deep monitoring, for example:

  // Inside the module
  namespaced: true,
  actions: {
    someAction: {
      root: true, // This action will be registered in the global space
      handler (namespacedContext, payload) { ... }
    }
  }
copy

Remember our auxiliary function above? If we open the namespace inside the module, how can we use auxiliary functions?

The usage of mapGetters and mapState are similar. The usage of mapActions and mapMutations is not bad. I won't repeat the demonstration here

We have three ways to use it

const moduleA = {
  namespaced: true,
  state: () => ({ count: 5 }),
  mutations: {
    addMutation (state) {...}
  },
  actions: {
    addAction ({commit}) {...}
  }
}

export default new Vuex.Store({
  state: {
    count: 1
  },
  modules: {
    moduleA // moduleA:moduleA uses the syntax of ES6 and is abbreviated as moduleA
  }
})



// =>It can be simplified as:

computed: {
  ...mapState('moduleA', {
    count: state => state.count
  })
}
copy

First: take the path when using

import { mapState, mapMutations } from 'vuex'

computed: {
  ...mapState({
    count: state => state.moduleA.count // => 5
  })
},
methods: {
  ...mapMutations([
    'moduleA/addMutation' // this['moduleA/addMutation '] ()
  ])
  ...mapMutations({
    addMutation: 'moduleA/addMutation' // this.addMutation()
  })
}
copy

The second method: pass the module name in the first parameter

import { mapState, mapMutations } from 'vuex'

computed: {
  ...mapState('moduleA', {
    count: state => state.count // => 5
  })
},
methods: {
  ...mapMutations('moduleA', [
    'addMutation' // this.addMutation()
  ])
  ...mapMutations('moduleA', {
    addMutation: 'addMutation' // this.addMutation()
  })
}
copy

Third: use createNamespacedHelpers

import { createNamespacedHelpers } from 'vuex'
const { mapState, mapMutations } = createNamespacedHelpers('moduleA')

computed: {
  ...mapState({
    count: state => state.count // => 5
  })
},
methods: {
  ...mapMutations([
    'addMutation' // this.addMutation()
  ])
  ...mapMutations({
    addMutation: 'addMutation' // this.addMutation()
  })
}
copy

Module reuse

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

  • Create multiple store s that share the same module (for example, when the runInNewContext option is false or 'once', to avoid stateful singletons in server-side rendering)
  • Register the same module multiple times in a store

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

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

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

Expand knowledge

v-model bidirectionally binds values in state

The official does not recommend that we directly use the v-model to bind the value in the state. And if we turn on the strict mode, we will report an error. If we use the vue thinking to solve this problem, we use the v-model to bind a value, then listen to the change of the value, and then use commit to change the value in the state. This is inevitably too cumbersome, The most elegant way officially recommended is to use the getter and setter properties of calculated properties

// ...
mutations: {
  updateMessage (state, message) {
    state.obj.message = message
  }
}
copy
<input v-model="message">
copy
// ...
computed: {
  message: {
    get () {
      return this.$store.state.obj.message
    },
    set (value) {
      this.$store.commit('updateMessage', value)
    }
  }
}
copy

Well, that's all for the content of this article. Time is short, knowledge is numerous, and learning skills are not good. It's hard to avoid mistakes. If any of you find something wrong in the article, I hope you can correct it. Thank you. I hope you can help us. I wish you all a smooth job 😄

Posted by skyloon on Thu, 11 Aug 2022 01:37:27 +0930