This article will show you how to manually implement a mini version of Vuex, which we callKStore)

What we want to do with KStore:

  • state
  • commit
  • dispatch
  • getters

Environment set up

The main thing to do in this step is not to use the official VUex library, but to use our own.

  • Create a project

    vue create myapp
    npm run serve
    Copy the code
  • Create the SRC /KStore/index.js and SRC /KStore/vuex.js files

    / / SRC/KStore/index. Js file
    import Vue from "vue";
    import Vuex from "./vuex";
    Vue.use(Vuex);
    export default new Vuex.Store({
      state: {
        counter: 1,},getters: {
        doubleCounter(state) {
          return state.counter * 2; }},mutations: {
        add(state) {
          state.counter += 1; }},actions: {
        add({ commit }) {
          commit("add"); }},modules: {},});/ / SRC/KStore/vuex js file
    class Store {
        // 2. Implement state
        
        // 3. Implement commit
        
        // 4. Implement dispatch
        
        // implement getters
    }
    function install() {
        1. Implement the install method
    }
    export default {
      Store,
      install,
    };
    
    
    Copy the code
  • Modify the main.js file

    import store from "./store";
    / / changed to
    import store from "./KStore";
    Copy the code
  • Add the use of vuex in home.vue for our test

    <template>
      <div class="home">
        <p style="font-size: 30px">State: {{$store.state.counter}}<br />Getters: {{$store. Getters. DoubleCounter}}<br />
    
          <button @click="$store.commit('add')">Mutation +</button>
          &nbsp;
          <button @click="$store.dispatch('add')">Actions +</button>
        </p>
      </div>
    </template>
    Copy the code
  • Look at the browser, the error is ok, then we implement our vuex

The install method

Install method, is a necessary method for each plug-in is also the format, mainly for vue prototype extension method, we this is no exception.

The code looks like this:

let Vue;
class Store {}

function install(_Vue) {
  Vue = _Vue;
  Vue.mixin({
    beforeCreate() {
      // Check if because we only passed store in the root instance, every component will execute if if
      if (this.$options.store) {
        Vue.prototype.$store = this.$options.store; }}}); }export default {
  Store,
  install,
};
Copy the code

If you look at the browser at this point, $Store is already accessible.

state

usage

this.$store.state.xxx
Copy the code

Implementation approach

$options: new Vue(); commit: new Vue(); commit: new Vue();

In the previous article: Writing the Mini version of Vue-Router, we used a responsive approach. Today we are going to use a new vue () approach. Hahaha!!

Matters needing attention

1. Why is $$state used here?

If a variable in data starts with $$, then Vue will not delegate that variable. This mechanism is internal to Vue. Remember that.

Code implementation

let Vue;
class Store {
  constructor(options) {
    this.$options = options;
    this._vm = new Vue({
      data() {
        return {
          $$state: options.state, }; }}); }/ / implementation of the state
  get state() {
    return this._vm._data.$$state;
  }
  set state(val) {
    console.log("State value cannot be changed");
    return; }}function install(_Vue) {... }export default {
  Store,
  install,
};
Copy the code

Comment out the reference to getters in the home.vue file, otherwise you will not see the effect and will report an error!!

commit

usage

this.$store.commit("add".1);
Copy the code

Implementation approach

First, we determined to execute the mutation[event] method based on the parameters passed in the COMMIT method, and then passed this. State into FN as a parameter. This is the process of realizing the commit.

Code implementation

let Vue;
class Store {
  constructor(options) {
    this.$options = options;
    this._vm = new Vue({
      data() {
        return {
          $$state: options.state, }; }}); }/ / implementation of the state
  get state() {
    return this._vm._data.$$state;
  }
  set state(val) {
    console.log("You can only change the value of state by committing");
    return;
  }

  // Implement commit
  commit(event, payload) {
    const fn = this.$options.mutations[event]; // Find the corresponding function in mutations
    if(! fn) {console.error("No such mutation method");
      return;
    } // Check whether this method exists
    fn(this.state, payload); // Execute this method by passing in the value of state}}function install(_Vue) {... }export default {
  Store,
  install,
};
Copy the code
  • Results demonstrate

    Click the mutation button, and the value is incremented by one. So, perfect implementation commit, is not easy!

dispatch

usage

// Declared in actions
{
    actions: {add({commit},payload){
            commit("add"); }}}// called in the page
this.$store.dispatch("add".1);
Copy the code

Implementation approach

The dispatch implementation works the same way as the COMMIT implementation, passing in the event parameter to determine which function to execute from actions.

Matters needing attention

Commit is called in dispatch, so we need to pay attention to the execution context of the commit, which in plain English refers to this

Code implementation

let Vue;
class Store {
  constructor(options) {
    this.$options = options;
    this._vm = new Vue({
      data() {
        return {
          $$state: options.state, }; }}); }get state() {
    return this._vm._data.$$state;
  }
  set state(val) {
    console.log("You can only change the value of state by committing");
    return;
  }
  commit(event, payload){... }// Implement the dispatch method
  dispatch(event, payload) {
    const fn = this.$options.actions[event];// Find the function in the corresponding actions
    if(! fn) {console.error("No such mutation method");
      return;
    }
    fn(this, payload); }}function install(_Vue) {... }export default {
  Store,
  install,
};

Copy the code

If the above code is implemented, the following error will be reported:

The solution, of course, is to specify this via call, apply, and bind, where bind is more appropriate.

let Vue;
class Store {
  constructor(options) {
    this.$options = options;
    this._vm = new Vue({
      data() {
        return {
          $$state: options.state, }; }});this.commit = this.commit.bind(this);// Important code, bind this to.}... }function install(_Vue) {... }export default {
  Store,
  install,
};
Copy the code
  • Results demonstrate

getters

usage

this.$store.getters.xxx
Copy the code

Implementation approach

Getters is a read-only property, and we can use closures to hold references to functions and set computed at the same time

Code implementation

let Vue;
class Store {
  constructor(options) {
    this.$options = options;
   
    // Explicitly bind this to dispatch (call commit in dispatch,commit is undefined)
    this.commit = this.commit.bind(this);


    / / implementation getters
    this.getters = {};
    let computed = {};
    var store = this;
    Object.keys(this.$options.getters).forEach((key) = > {
      const fn = this.$options.getters[key];
      computed[key] = function () {
        return fn(store.state);
      };
      Object.defineProperty(this.getters, key, {
        get() {
          return store._vm[key];// Computed values are proxied to instances, so access _vm directly}}); });this._vm = new Vue({
      data() {
        return {
          $$state: options.state,
        };
      },
      computed,
    });
  }



  get state() {
    return this._vm._data.$$state;
  }
  set state(val) {
    console.log("You can only change the value of state by committing");
    return;
  }
  commit(event, payload){... }dispatch(event, payload){... }}function install(_Vue) {... }export default {
  Store,
  install,
};

Copy the code

conclusion

In the last article, I wrote the implementation of a mini version of VUE-Router, and in this article, I implemented a mini version of Vuex, which is actually for the convenience of future review. It is easy to write a detailed point, to share with you, is not very good at writing articles, hope forgive me!

  • Finally paste the complete code (can run directly)
let Vue;
class Store {
  constructor(options) {
    // Receives the parameters passed by the user
    this.$options = options;

    // Explicitly bind this to dispatch (call commit in dispatch,commit is undefined)
    this.commit = this.commit.bind(this);

    / / implementation getters
    this.getters = {};
    let computed = {};
    var store = this;
    Object.keys(this.$options.getters).forEach((key) = > {
      const fn = this.$options.getters[key];
      computed[key] = function () {
        return fn(store.state);
      };
      Object.defineProperty(this.getters, key, {
        get() {
          returnstore._vm[key]; }}); });// Reactive processing
    this._vm = new Vue({
      data() {
        return {
          $$state: options.state,
        };
      },
      computed,
    });
  }

  / / implementation of the state
  get state() {
    return this._vm._data.$$state;
  }
  set state(val) {
    console.log("You can only change the value of state by committing");
    return;
  }

  / / the commit
  commit(event, payload) {
    const fn = this.$options.mutations[event]; // Find the corresponding function in mutations
    if(! fn) {console.error("No such mutation method");
      return;
    } // Check whether this method exists
    fn(this.state, payload); // Execute this method by passing in the value of state
  }

  / / dispatch
  dispatch(event, payload) {
    const fn = this.$options.actions[event];
    if(! fn) {console.error("No such mutation method");
      return;
    }
    fn(this, payload); }}function install(_Vue) {
  Vue = _Vue;
  Vue.mixin({
    beforeCreate() {
      // If we pass store only in the root instance
      if (this.$options.store) {
        Vue.prototype.$store = this.$options.store; }}}); }export default {
  Store,
  install,
};

Copy the code