Hello, I’m Sanxin Lin. Vuex is a state management mode developed specifically for vue.js applications. It uses centralized storage to manage the state of all components of an application, and rules to ensure that the state changes in a predictable manner.

Under what circumstances should I use Vuex?

Vuex helps manage shared state and comes with more concepts and frameworks. This requires a balance between short – and long-term benefits.

If you don’t plan to develop large, single-page applications, using Vuex can be tedious and redundant. That’s true — if your application is simple enough, you’re better off not using Vuex. A simple Store mode (such as a new window) should suffice. However, if you need to build a medium to large single-page application, you are likely to be thinking about how to better manage state outside of components, and Vuex would be a natural choice. To quote Dan Abramov, author of Redux:

The Flux architecture is like glasses: you’ll know when you need it.

Review the use of Vuex

The installation

Yarn

yarn add vuex
Copy the code

NPM

npm install vuex --save
Copy the code

In a modular packaging system, you must explicitly install Vuex via vue.use () :

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

Vue.use(Vuex)
Copy the code

Registered store

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

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

new Vue({
  el: '#app',
  store / / register
})
Copy the code

State

  1. Normal use
const Counter = {
  template: `<div>{{ count }}</div>`.computed: {
    count () {
      return this.$store.state.count
    }
  }
}
Copy the code

Each time this.$store.state.count changes, the evaluated property is recalculated and the associated DOM is updated.

  1. Auxiliary function

When a component needs to obtain multiple states, declaring them as computed properties can be repetitive and redundant. To solve this problem, we can use the mapState helper function to help us generate computed properties that allow you to press the key fewer times:

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

export default {
  // ...
  computed: mapState({
    // The arrow function makes the code more concise
    count: state= > state.count,

    // Pass the string argument 'count' as' state => state.count '
    countAlias: 'count'.// In order to be able to use 'this' to get local state, you must use regular functions
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}
Copy the code

We can also pass an array of strings to mapState when the name of the mapping’s computed property is the same as the name of the state’s child node.

computed: mapState([
  // Map this.count to store.state.count
  'count'
])
Copy the code

Object expansion operator

computed: {
  localComputed () { / *... * / },
  // Use the object expansion operator to mix this object into the external object. mapState({// ...})}Copy the code

Getters

  1. Normal use

The Getter accepts state as its first argument:

const store = new Vuex.Store({
  state: {
    todos: [{id: 1.text: '... '.done: true },
      { id: 2.text: '... '.done: false}},getters: {
    doneTodos: state= > {
      return state.todos.filter(todo= > todo.done)
    }
  }
})
Copy the code

Getters can also take other getters as second arguments:

getters: {
  // ...
  doneTodosCount: (state, getters) = > {
    return getters.doneTodos.length
  }
}
Copy the code

We can easily use it in any component:

computed: {
  doneTodosCount () {
    return this.$store.getters.doneTodosCount
  }
}
Copy the code

Note that getters are cached as part of Vue’s responsive system when accessed through properties. (The same is true for computed caches, which I’ll cover in a later article.)

You can also implement passing parameters to the getter by having the getter return a function. This is useful when you are querying arrays in the Store.

getters: {
  // ...
  getTodoById: (state) = > (id) = > {
    return state.todos.find(todo= > todo.id === id)
  }
}
Copy the code
store.getters.getTodoById(2) // -> { id: 2, text: '... ', done: false }
Copy the code
  1. Auxiliary function

The mapGetters helper function simply maps getters in a store to locally computed properties:

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // Use the object expansion operator to mix getters into a computed object. mapGetters(['doneTodosCount'.'anotherGetter'.// ...])}}Copy the code

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

. mapGetters({/ / the ` enclosing doneCount ` mapping for ` enclosing $store. Getters. DoneTodosCount `
  doneCount: 'doneTodosCount'
})
Copy the code

Muations

  1. Normal use

Mutations in Vuex are very similar to events: each mutation has an event type (type) of a string and a callback function (handler).

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state, n) { // N is a parameter that can be set. This parameter is also called "load".
      // Change the status
      state.count++
    }
  }
})
Copy the code
/ / use
this.$store.commit('increment'.10)
Copy the code
  1. Auxiliary function
import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment'.// Map 'this.increment()' to 'this.store.com MIT ('increment')'

      MapMutations' also supports load:
      'incrementBy' // Map 'this.incrementby (amount)' to 'this.store.com MIT ('incrementBy', amount)'
    ]),
    ...mapMutations({
      add: 'increment' // Map 'this.add()' to 'this.store.com MIT ('increment')'}}})Copy the code

Mixing asynchronous calls in mutation can make your program difficult to debug. For example, when you call two mutations that contain asynchronous callbacks to change state, how do you know when to call back and which callback came first? That’s why we want to distinguish between these two concepts. In Vuex, mutations are all synchronous transactions

Action

Action is similar to mutation, except that:

  • The Action commits the mutation instead of directly changing the state.
  • An Action can contain any asynchronous operation.
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
      // The Action function takes a context object that has the same methods and properties as the Store instance
     incrementAsync (context , n) { // Can transmit "load" n
       setTimeout(() = > {
         context.commit('increment')},1000)}}})Copy the code
/ / execution
// Distribute as a payload
store.dispatch('incrementAsync', {
  amount: 10
})

// Distribute as objects
store.dispatch({
  type: 'incrementAsync'.amount: 10
})
Copy the code
  1. Auxiliary function
import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment'.// Map 'this.increment()' to 'this.$store.dispatch('increment')'

      // 'mapActions' also supports loads:
      '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
  1. Combination of the Action
// Suppose getData() and getOtherData() return a Promise
actions: {
  async actionA ({ commit }) {
    commit('gotData'.await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // Wait for actionA to finish
    commit('gotOtherData'.await getOtherData())
  }
}
Copy the code

Module

By using a single state tree, all the state of the application is concentrated into one large object. When the application becomes very complex, the Store object can become quite bloated.

To solve this problem, Vuex allows us to split the store into modules. Each module has its own state, mutation, action, getter, and even nested submodules — split the same way from top to bottom:

const moduleA = {
  state: () = >({... }),mutations: {... },actions: {... },getters: {... }}const moduleB = {
  state: () = >({... }),mutations: {... },actions: {... }}const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA status
store.state.b // -> moduleB status
Copy the code

For the mutation and getter inside the 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
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      // The 'state' object here is the local state of the module
      return state.count * 2}}}Copy the code

Similarly, for actions within the module, the local state is exposed through context.state, and the root node state is context.rootState:

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

For getters inside modules, the root node state is exposed as a third argument:

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

The namespace part of the module, we’ll come back to that later

Simple principle implementation

Interpretation of the

Looked at the Vuex source file, found that there are a lot of, HERE I will talk about our most commonly used part of the function of the source code

If you have used Vuex, you can call this.$store. XXX in a page or component, so you can just assign the store object to the $store variable in the page or component

The principle of Vuex is to use global Mixin, you create a store object, mixed into each Vue instance, so what is global Mixin? Here’s an example:

import Vue from 'vue'
// Mixed globally
Vue.mixin({
  created () {
      console.log('I'm Lin Sanxin')}})// All Vue instances created later will print 'I am Lin Sanxin'
const a = new Vue({
  // There is nothing here but output 'I am Lin Sanxin'
})
=> < span style = "box-sizing: border-box; color: RGB (74, 74, 74);
const b = new Vue({
  // There is nothing here but output 'I am Lin Sanxin'
})
=> < span style = "box-sizing: border-box; color: RGB (74, 74, 74);
Copy the code

For those of you who understand the example above, replace the console.log(‘ I am Lin SAN Xin ‘) code with a piece of code that does the same thing: assign the store value to the $store property of the instance.

Code implementation

directory

  1. vuex.js
// vuex.js
let Vue;

// Install method is set because vue.use (XXX) will execute the install method of XXX
const install = (v) = > { // The v parameter is responsible for receiving vUE instances
    Vue = v;
    // Mixed globally
    Vue.mixin({
        beforeCreate() {
            if (this.$options && this.$options.store) {
                // The root page directly assigns its own $store to its own $store,This also explains why to use vuex we first put the store in the root Vue instance in the entry file main.jsthis.$store = this.$options.store;
            } else {
                // Assign the parent $store to its own $store except the root page
                this.$store = this.$parent && this.$parent.$store; }}})}// Create the Store class
class Store {
    constructor(options) { // Options accepts incoming store objects
        this.vm = new Vue({
            // Make sure state is responsive
            data: {
                state: options.state
            }
        });
        // getter
        let getters = options.getters || {};
        this.getters = {};
        console.log(Object.keys(this.getters))
        Object.keys(getters).forEach(getterName= > {
            Object.defineProperty(this.getters, getterName, {
                get: () = > {
                    return getters[getterName](this.state); }})})// mutation
        let mutations = options.mutations || {};
        this.mutations = {};
        Object.keys(mutations).forEach(mutationName= > {
            this.mutations[mutationName] = payload= > {
                mutations[mutationName](this.state, payload); }})// action
        let actions = options.actions || {};
        this.actions = {};
        Object.keys(actions).forEach(actionName= > {
            this.actions[actionName] = payload= > {
                actions[actionName](this.state, payload); }})}// When the state is obtained, return it directly
    get state() {
        return this.vm.state;
    }
    The commit method executes the 'name' method of mutations
    commit(name, payload) {
        this.mutations[name](payload);
    }
    // Dispatch method, execute the 'name' method of actions
    dispatch(name, payload) {
        this.actions[name](payload); }}// Expose the install method and class Store
export default {
    install,
    Store
}
Copy the code
  1. index.js
// index.js
import Vue from 'vue';
import vuex from './vuex'; // Introduce the objects exposed by vuex.js
Vue.use(vuex); // The install method in the vuex object is executed, that is, the mixin is mixed globally

// Instantiate a Store class and expose it
export default new vuex.Store({
    state: {
        num: 1
    },
    getters: {
        getNum(state) {
            return state.num * 2; }},mutations: { in (state, payload) {
            state.num += payload;
        },
        de(state, payload){ state.num -= payload; }},actions: { in (state, payload) {
            setTimeout(() = > {
                state.num += payload;
            }, 2000)}}})Copy the code
  1. main.js
// main.js
import Vue from 'vue';
import App from './App.vue'

import store from './store/index'; // Introduce index.js


new Vue({
    store, // Hang the store on the root instance
    el: '#app'.components: {
        App
    },
    template: '<App/>',})Copy the code

So far, state, mutations, getter, actions of VUex are simply realized. In the future, I will write an article about implementing Mudule

Vue is not advocating global mixins, even mixins are not advocating to use, don’t mess with oh!