A preface,

This article more like my a vuex study notes, learning resources are mainly from the official documentation tutorial, the official tutorials have been more careful, parts have their own do not understand of place, so also checked other information to assist in your understanding, this’s note on the official tutorials with some added content, the hope can give you some reference value, In addition, THANKS to other Internet sharing knowledge of the big guy, let me take less detours! If the article does not understand the place in place, please also criticize and correct!

2. The initial experience of Vuex

1. Why Vuex

Using the Vue in the process of development, we often encounter a state may use between multiple components, such as when we do the project used in the user’s information, what is the nickname, face it, these information will be used in the different components, once change the state, we hope that other components also follow change, such as user prepaid phone 100 yuan, Or the nickname is changed, so the state management mode is needed for centralized management. Details about Vuex can be found on the official website.

2. Preparation before learning

I will not go into details about the use of scaffolding this time. You can move to Vue CLI. When using the project generated by Vue CLI, you will be asked to select Store, and a store.js will be generated for you on the page, which is the original Store. The inside structure is as follows:

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

Vue.use(Vuex)

export default new Vuex.Store({
  state: {

  },
  mutations: {

  },
  actions: {

  }
})
Copy the code

Third, the State

At the heart of Vuex is the store. This store instance is injected into all child components. The state property in the store holds our state.

export default new Vuex.Store({
  state: {
    count: 10
  },
})
Copy the code

So we have a centrally managed state count. How do other components get this count? We can calculate the properties to get this count:

export default {
  data() {
    
  },
  computed: {
    count() {
      return this.$store.state.count; }}}Copy the code

Because the store option is registered in the root instance, the store instance is injected into all the children of the root component, which can be accessed through this.$store. By evaluating attributes, we can use template syntax to call count from within the template, as follows:

<template>
  <div>
    <p>{{ count }}</p>
  </div>
</template>
Copy the code

mapState

Sometimes it is necessary to obtain multiple states, but it is troublesome to call multiple times with computed attributes. Here, the mapState method is used to obtain state. Using mapState requires the introduction of this method

import { mapState } from 'vuex';
Copy the code

Note: Using the mapState method here, you write computed differently. For example, by default your computed property looks like this:

data() {return{
    msg: 'hello '
  }
}
computed: {
  msg() {
    return this.msg + 'world! '; }}Copy the code

So if you use mapState, you need to write computed, put MSG () in mapState, otherwise you’ll get an error.

data() {return{
    msg: 'hello '.localCount: 20
  }
}
computed: mapState({
  msg() {// initialreturn this.msg + 'world! '; }, // use mapState to import state count(state) from store {return state.count;
  },
  name(state) {
    return state.firstName + ' '+ state.lastName; }, mixCount(state) {// Combine store and component state for calculationreturnstate.count + this.localCount; }})Copy the code

If you use the expansion operator… , then computed attributes do not need to be modified and are written as normal

Computed: {// Write this way if you use expansion, otherwise use another way or report an errormsg() {
    return this.$store.state.msg; }, // return one status count, // return multiple you can write like this... mapState(['count'.'firstName'.'lastName'])
  ...mapState(['count'])},Copy the code

Four, Getter

A getter is a common extracted part of a state. When the state is filtered, we can use the getter to return it to the component. For example, we define a list array in the state section:

export default new Vuex.Store({
  state: {
    list: [1, 2, 3, 4, 5, 6, 7, 8]
  },
});
Copy the code

We want to filter out even numbers in the array and then use them in the component, so the filtering can be done in the getter.

exportdefault new Vuex.Store({ state: { list: [1, 2, 3, 4, 5, 6, 7, 8] }, getters: ModifyArr (state) {// Generalize the getterreturn state.list.filter((item, index, arr) => {
        returnitem % 2 == 0; })}, getLength(state, getter) {// Pass the getter inside the method, call modifyArr to calculate the lengthreturngetter.modifyArr.length; }});Copy the code

Then call the getter in the computed value of the other component to get the desired state

computed: {
    list() {
      return this.$store.getters.modifyArr; }},Copy the code

mapGetters

The mapGetters helper function simply maps the getters in the store to local computed properties. When we want to introduce multiple getters into a component, we can use mapGetter:

import {mapGetters} from 'vuex';
Copy the code

For example, modifyArr, getLength as defined above. We want to introduce these two and get their values:

computed: { ... mapGetter(['modifyArr'.'getLength'])}Copy the code

You can, of course, give it an alias, not necessarily the name defined by getters in the store:

computed: {
  mapGetter({
    arr: 'modifyArr'// map 'this.arr' to 'this'.$store.getters. ModifyArr ', the same length as below:'getLength'})}Copy the code

If your computed property contains other methods, you have to use the expansion operator, which is a little different from mapState. If you write other computed properties in the mapGetter, you will get an error saying that there is no getter, so write them like this:

computed: {
  msg() {
    return this.num * 10;
  },
  ...mapGetters([
    'modifyArr'.'getLength'])}Copy the code

Or specify an alias

computed: { 
  msg() {
    return this.num * 10;
  },
  ...mapGetters({
    getList: 'modifyArr',
    length: 'getLength'})}Copy the code

Then call from the template:

<template> <div> <h2>mapGetters use demonstration </h2> <p> your number: {{MSG}}</p> <p> Your array length is: {{length}}</p> <ul> <li V-for ="(item, index) in getList" :key="index">{{ item }}</li>
    </ul>
  </div>
</template>
Copy the code

Five, the Mutation

When we need to change the state in store, we do not change them directly in the component, but change them through mutation, which is convenient to track the state change. For example, if we have a count variable in state, we click the plus or minus button to control its value:

mutations: { add(state) { state.count++; }, reduce(state) { state.count--; }},Copy the code

In the other component, we trigger the change by defining methods and binding time, using commit:

methods: {
  add() {
    this.$store.commit('add');
  },
  reduce() {
    this.$store.commit('reduce'); }}Copy the code

Submit the load

This is to commit extra parameters at commit time. For example, I passed extra values to count:

Mutations: {loadAdd(state, payload) {// Add the extra parameters state.count += payload; }},Copy the code

Then use it inside the component:

methods: {
  loadAdd() {
    this.$store.commit('loadAdd', 100); // Pass extra arguments}}Copy the code

The official documentation suggests that payloads (that extra parameter) should be passed using objects, which can contain multiple fields and make the mutation easier to read, like this:

this.$store.commit('loadAdd', { extraCount: 100 }); // Pass extra argumentsCopy the code

We can also write all the arguments in one object when we call commit:

this.$store.commit( {
  type: 'addLoad'extraCount: 100 }); // Pass extra argumentsCopy the code

The Mutation complies with the Vue response rules

This basically means that you need to follow the response criteria when adding additional data to state during redevelopment. Mutation in Vuex requires the same precautions as mutation in Vue:

  • It is best to initialize all required properties in your store in advance.

  • Set (obj, ‘newProp’, 123), or replace an old object with a new one. For example, use the stage-3 object expansion operator

We can write this:

state.obj = { ... state.obj, newProp: 123 }Copy the code

For example, I declared a method in mutation

// Vue. Set (state, payload) {// Vue. Set (state, payload)'newProp'.'Add a new value! '); // State = {... state, newProp:'Add a new value! '} // This.replacEstate ({... state, newProp:'Add a new value! '})}Copy the code

Call the method from the component and define a method:

addNewProp() {
  this.$store.commit('addNewState'{}); }Copy the code

After executing this method, the computed property of the component is updated to state in a timely manner:

newMsg() {
  return this.$store.state.newProp || 'No new value added yet';
}
Copy the code

Display instantly in the template and do not affect other states:

<p> Added new values: {{newMsg}}</p> <div>< button@click ="addNewProp"</button></div>Copy the code

Mutation must be a synchronization function

The following must be avoided (direct official example blessing) :

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}
Copy the code

mapMutations

The previous several functions are the same, are to simplify the call, using the method as follows:

import {mapMutations} from 'vuex';
Copy the code

Then use it in the component’s methods, using the official code:


exportdefault { // ... methods: { ... mapMutations(['increment', // Map 'this.increment()' to 'this.$store.commit('increment') // 'mapMutations' also supports payload:'incrementBy'// Map 'this.incrementBy(amount)' to 'this.$store.commit('incrementBy', amount)` ]), ... mapMutations({ add:'increment'// Map 'this.add()' to 'this.$store.commit('increment') `}}}Copy the code

Sixth, the Action

Action is similar to mutation, except that:

  • The Action commits mutation rather than a direct state change.
  • Actions can contain any asynchronous operation. Mutation can only contain synchronous transactions, so you need Action to handle asynchronous transactions. The Action controls the asynchrony process, and then calls the methods in mutation to change the state. Here I’ll post the code directly to make it clear. First, I define a state product:
state: {
  product: 'car'
}
Copy the code

Then define a method in mutation:

changeProduct(state, payload) {
  state.product = payload.change;
}
Copy the code

Defined in action:

actions: { changeProduct(context, // call the changeProduct method of the mutation (payload) {// call the changeProduct method of the mutation () // call the changeProduct method of the mutation () // context.com ()'changeProduct', {change: 'ship'}); // Change the asynchronous mode //setTimeout(() => {
    //   context.commit('changeProduct', {change: 'ship'}); }, 1500) // Loadlet temp = 'ship+' + payload.extraInfo; 
    setTimeout(() => {
      context.commit('changeProduct', {change: temp}); }}}, 1500)Copy the code

Define event trigger distribution in component Methods:

methods: {
  selectProduct() {
    // this.$store.dispatch('changeProduct'// Load mode distribution // this.$store.dispatch('changeProduct', {
    //   extraInfo: 'sportcar'//}) // or this.$store.dispatch({
      type: 'changeProduct',
      extraInfo: '->sportcar'})}},Copy the code

That’s a simple action!

mapActions

The map helper function is used to map the methods in the action function. The map helper function is used to map the methods in the action function.

import {mapActions} from 'vuex';
Copy the code
exportdefault { // ... methods: { ... mapActions(['increment', // Map 'this.increment()' to 'this.$store.dispatch('increment') '//' mapActions' also supports payloads:'incrementBy'// Map 'this.incrementBy(amount)' to 'this.$store.dispatch('incrementBy', amount)` ]), ... mapActions({ add:'increment'// Map 'this.add()' to 'this.$store.dispatch('increment') `}}}Copy the code

Sometimes we want to know the status of an action after its asynchronous execution before we modify other information. We can use promises to do this. Here we declare a state inside state:

State: {userInfo: {// This variable is used to test the combination variable name:'lee',
    age: 23
  }
}
Copy the code

Next, declare mutation:

Mutations: {// The following test combination action changeInfo(state, payload) {state.userinfo.name ='lee haha'; }}Copy the code

Statement of the action:

actions: {
  changeInfo(context, payload) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        context.commit('changeInfo'); resolve(); }, 2000)})}}Copy the code

At this point we define a method in the component to distribute the action:

data() {
  return {
    status: 'The message hasn't been modified! '
  }
}
methods: {
  modifyInfo() {
    this.$store.dispatch('changeInfo').then(() => {
      this.status = 'Information modified successfully'; }); }}Copy the code

Template display:

Combination of < the template > < div > < h2 > action < / h2 > < p > {{status}} < / p > < p > {{info. Name}} < / p > < div > < button @ click ="modifyInfo"</button></div> </div> </template>Copy the code

When we click modify information, the action will be sent. When the modification is successful, the display information mentioned above will be synchronized. Other defined action methods can also be used with each other.

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  },
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')}}}Copy the code

Seven, the Module

The module part, as the name suggests, becomes quite bloated when all the state is concentrated in one object, which is where the module management approach is needed. For example, I defined two modules in the Store:

// define moduleA const moduleA = {state: {name:'lee', age: 23, }, mutations: { }, getters: { }, actions: { } }; // define moduleB const moduleB = {state: {name:'wang',
    age: 22
  },
  mutations: {

  },
  getters: {

  },
  actions: {

  }
}
Copy the code

Then declare the module in Vuex:

exportdefault new Vuex.Store({ modules: { ma: moduleA, mb: moduleB }, state: { ........... // Other state}});Copy the code

This way, if we want to access the state of another module in the component, we can do this, for example, here I want to call the state in module B:

computed: {
  msg() {
    return this.$store.mb; // Return: {name:'wang', age: 22}
  }
}
Copy the code

For example, for an action within a module, the local state is context.state and the rootState is context.rootState. Here’s the official code:

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

For getters inside the module, the root node state is exposed as a third parameter:

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

Copy the code

So for the methods in getters, mutations, and Actions, we can call them just like the basic store. There is no scope limit, so let’s stick to the code. Here is the module B I defined in store.js:

const moduleB = {
  state: {
    name: 'wang',
    age: 22,
    desc: 'nope'
  },
  mutations: {
    modifyDesc(state, payload) {
      state.desc = payload.newMsg;
    }
  },
  getters: {

  },
  actions: {

  }
}
Copy the code

Inside the component, I define the following:

< the template > < div > < h2 > 7, the module using the sample < / h2 > < div > < p > name: {{name}} < / p > < p > description: {{desc}} < / p > < button @ click ="handleClick"</button> </div> </template> <script>export default {
  data() {
    return {
      name: this.$store.state.mb.name,
      // desc: this.$storeState.mb. desc Note that if this refers to a state that needs to be changed in the store, it must be written in // computed property, otherwise it cannot be reported to the view in time}}, computed: {desc() {
      return this.$store.state.mb.desc;
    }
  },
  methods: {
    handleClick() {
      this.$store.commit('modifyDesc', {newMsg: 'lao wang is beautiful! '});
    }
  },
}
</script>
Copy the code

This allows you to call the mutation methods, as well as getters and actions

2019/09/09 Added the rest

Namespace module

Mutations, Actions, and getters are registered globally by default, and you can call them directly, but if you want your module to have more encapsulation and reusability, you can add Namespaced: True to make it a module with a namespace. When a module is registered, all its getters, actions, and mutations are automatically named according to the path the module was registered with. First I create a new js file that declares module C:

/* * This file declares the module C */export const moduleC = {
  namespaced: true,
  state: {
    name: 'moduleC',
    desc: 'This is module C, used to test namespaces! ',
    list: [1, 2, 3, 4]
  },
  getters: {
    filterList(state) {
      return state.list.filter((item, index, arrSelf) => {
        returnitem % 2 ! = = 0; }); } }, mutations: { modifyName(state, payload) { state.name = payload.newName; } }, actions: { } }Copy the code

Then add the following to store.js:

import { moduleC } from './module_c.js';

export default new Vuex.Store({
  modules: {
    mc: moduleC
  },
});
Copy the code

To make this module a module with a namespace, declare the namespaced attribute above: The mutations, getters, and actions methods will be called on an extra path. For example, I call the mutations method in the component (the same goes for getters and Actions) :

methods: {
  modify() {
    // this.$store.commit('mc/modifyName', {
    //   newName: 'Namespace module C'
    // })
    this.$store.commit({
      type: 'mc/modifyName',
      newName: 'Namespace module C'}}})Copy the code

For example, if you have your namespaced: true mode declared, you can use the following code:

const store = new Vuex.Store({
  modules: {
    account: {
      namespaced: true, // Module assets state: {... }, // the state in the module is already nested, and using the 'namespaced' attribute doesn't affect it.isAdmin() {... } // -> getters['account/isAdmin']
      },
      actions: {
        login() {... } // -> dispatch('account/login')
      },
      mutations: {
        login() {... } // -> commit('account/login'}, // the nested module modules: {// inherits the parent module's namespace myPage: {state: {... }, getters: {profile() {... } // -> getters['account/profile'}}, // further nested namespace posts: {namespaced:true,

          state: { ... },
          getters: {
            popular() {... } // -> getters['account/posts/popular']}}}}}})Copy the code

Access global content within a module with a namespace

If you want to access global content inside the module’s getters, Mutations, and Actions, Vuex already packages it, you just need to pass in a few more parameters. Here’s a wave of official demos, simple and clear:

modules: {
  foo: {
    namespaced: true, getters: {// In the getter of this module, 'getters' is localized // You can call' rootGetters' someGetter (state, getters, rootState, rootGetters) { getters.someOtherGetter // ->'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'}, someOtherGetter: state => { ... } }, actions: {// In this module, // They can accept the 'root' attribute to access the root dispatch or commit someAction ({dispatch, commit, getters, rootGetters }) { getters.someGetter // ->'foo/someGetter'
        rootGetters.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) { ... }}}}Copy the code

Use auxiliary functions mapState, mapGetters, mapMutations, and mapActions in the module

Because of namespaces, this approach is problematic in components. To use helper functions to map the contents of a module, you need to specify the space name to tell helper functions where to find them. For the mapSatate function, I declared MC in modules, so my space name would be MC:

computed: { ... mapState('mc'['name'.'desc'}}}}}}}Copy the code

Then write name desc in the template, or you can do this:

computed: { ... mapState('mc', {
    name(state) {
      return state.name;
    },
    desc(state) {
      returnstate.desc; }})},Copy the code

Mutations and getters work similarly for actions, mutations, and getters, which specify a space name, such as mutations for the declaration:

methods: { ... mapMutations('mc'['modifyName'])}Copy the code

If you really don’t want to put a space name in each helper function, Vuex offers another way to create helper functions based on a namespace using createNamespacedHelpers that return an object with a new component binding helper function bound to a given namespace value:

import { createNamespacedHelpers } from 'vuex';

const { mapState, mapMutations } = createNamespacedHelpers('mc');
Copy the code

This way you don’t need to specify a separate space name when writing auxiliary functions. Other similar, forgive me not to repeat!

Eight, epilogue

This article is a basic introduction, other content you are interested in the official website to browse (# funny survival). I have uploaded the relevant code to Github. If you are interested, download it and have a look! Demo code