Write your own version of VUEX

Keywords: Javascript Vue Attribute

Since VUEX can be used, there must be an install method inside, so we need to implement an install method first. When we use it, there is a this.$store attribute on each component, which contains the state, transitions, actions and getters in the state warehouse. Therefore, we need to mount a $store attribute on each component, specifically. As follows:

let Vue = null;
export function install(_Vue) {
  // To prevent duplicate registration
  if (Vue !== _Vue) {
    Vue = _Vue
  }
  Vue.mixin({
    beforeCreate() {
      const options = this.$options;
      if (options.store) {
        this.$store = options.store;
      } else if (options.parent && options.parent.$store) {
        this.$store = options.parent.$store;
      }
    }
  })
}

In order to slow down later, we need to encapsulate our own loop method to simplify code operation.

const forEach = (obj, cb) => {
  Object.keys(obj).forEach(key => {
    cb(key, obj[key]);
  })
}

Now we can introduce and use(vuex) normally, but at this time we haven't initialized the warehouse, because the original vuex needs to go to new Vuex.Store(), which can pass in some initialization parameters, such as state, transitions, actions, getters. Since it can be new, it means that it's a class, so now we write the store class. The specific implementation is as follows:

export class Store {
  constructor(options = {}) {
    // TODO....
  }
}

Well, at this time, the page will not report an error, and you can also get the value in the state through this.$store.state.xxx. However, the original state reset will cause the view update, so you need to set the state in the store to be responsive. The specific implementation is as follows:

export class Store {
  constructor(options = {}) {
    this.vm = new Vue({
      data(){
        return {
          state: options.state
        }
      }
    })
  },
  // Property accessor for class
  get state() {
    return this.vm.state
  }
}

Now we can set the value in the page to trigger the view update, because we have changed all the data into two-way binding, as long as the view change will trigger the view update.

At this time, when initializing the store, we also passed in the statistics, actions and getters. These data have not been processed, so now we can process these data first, and there are commit, dispatch and other attributes on the $store attribute, so we need to write all these attribute pages. The specific implementation is as follows:

export class Store {
  constructor(options = {}) {
    this.vm = new Vue({
      data(){
        return {
          state: options.state
        }
      }
    })
    
    this.getters = {};
    this.mutations = {};
    this.actions = {};

    // Processing getters responsive data
    let getters = options.getters;
    forEach(getters,(getterName, fn)=>{
      Object.defineProperty(this.getters, getterName, {
        get: () => {
          return fn(this.state)
        }
      })
    })

    // Get all synchronized update methods
    let mutations = options.mutations; 
    forEach(mutations,(mutationName, fn) => {
      this.mutations[mutationName] = (payload) => {
        fn(this.state, payload)
      }
    });

    // Get all asynchronous update operation methods
    let actions = options.actions; 
    forEach(actions,(actionName, fn) => {
      this.actions[actionName] = (payload) => {
        fn(this, payload);
      }
    })
  }


  commit = (type, payload) => {
    this.mutations[type](payload)
  }

  dispatch = (type, payload) => {
    this.actions[type](payload)
  }

  get state() {
    return this.vm.state
  }

}

At this point, the basic vuex can run normally! Is it very simple!!!!!

However, this is far more than!!!!!

We know that the native vuex also has the modules attribute, which can be nested with any layer of state, transitions, actions, getters, so we need to deal with this attribute as well.
See that there is a "modules" attribute in the native vuex attribute, which is a tree structure.

So we also generate an object with the structure of "modules tree" in the same way as the original.

// Format modules
class ModuleCollection {
  constructor(options) {
    // Register module register module as tree structure
    this.register([], options);
  }
  register(path, rootModule) {
    let module = { // Format module
      _rawModule: rootModule,
      _chidlren: {},
      state: rootModule.state
    }
    if (path.length == 0) {
      // If it's the root module, hang the module on the root instance
      this.root = module;
    } else {
      // Recursion uses the reduce method
      // Search by the "children" attribute
      let parent = path.slice(0, -1).reduce((root, current) => {
        return root._chidlren[current]
      }, this.root)
      parent._chidlren[path[path.length - 1]] = module
    }
    // Check whether there are modules in the current module. If there are modules, start to register again.
    if (rootModule.modules) {
      forEach(rootModule.modules, (moduleName, module) => {
        this.register(path.concat(moduleName), module)
      })
    }
  }
}

Then mount the ﹐ modules attribute to the instance in the Store class.

Store Class:
    // Format the data into a desired tree structure
    this._modules = new ModuleCollection(options);

So far, we have processed the modules into the desired structure, but the attributes under each module have not been mounted, so we need to mount the attributes of each module recursively, so the above methods for handling the attributes of mutations, atoms and getters need to be rewritten. The specific implementation is as follows:

/**
 * @explain { Install module}
 *    @param { store }  Whole store 
 *    @param { rootState }  Current root state
 *    @param { path }  Created for return
 *    @param { rootModule }  Install from root module
 */

const installModule = (store, rootState, path, rootModule) => {
  if (path.length > 0) {
    // [a]
    // It's the son. The son needs to find his father and put his status on it.
    let parent = path.slice(0, -1).reduce((root, current) => {
      return root[current]
    }, rootState)
    // vue cannot add non-existent attributes on the object, otherwise it will not cause view update.
    Vue.set(parent, path[path.length - 1], rootModule.state);
    // {age:1,a:{a:1}}
    // Realize the format of finding and hanging data
  }
  // The following code is getters actions mutation in the processing module
  let getters = rootModule._rawModule.getters;
  if (getters) {
    forEach(getters, (getterName, fn) => {
      Object.defineProperty(store.getters, getterName, {
        get() {
          return fn(rootModule.state); // Let the corresponding function execute
        }
      });
    })
  }
  let mutations = rootModule._rawModule.mutations;
  if (mutations) {
    forEach(mutations, (mutationName, fn) => {
      let mutations = store.mutations[mutationName] || [];
      mutations.push((payload) => {
        fn(rootModule.state, payload);
        // Publish lets all subscriptions execute in turn
        store._subscribes.forEach(fn => fn({ type: mutationName, payload }, rootState));
      })
      store.mutations[mutationName] = mutations;
    })
  }
  let actions = rootModule._rawModule.actions;
  if (actions) {
    forEach(actions, (actionName, fn) => {
      let actions = store.actions[actionName] || [];
      actions.push((payload) => {
        fn(store, payload);
      })
      store.actions[actionName] = actions;
    })
  }
  // Mount my son
  forEach(rootModule._chidlren, (moduleName, module) => {
    installModule(store, rootState, path.concat(moduleName), module)
  })
}

Then execute this processing function in the Store class to mount successfully.

/**
     * @explain { Install module}
     *    @param { this }  Whole store 
     *    @param { this.state }  Current root state
     *    @param { [] }  Created for return
     *    @param { this._modules.root }  Install from root module
     */
    installModule(this, this.state, [], this._modules.root);

At this time, our own vuex can run normally. You can write modules, state, transitions, actions, getters at will. Isn't it cool!!!!! Ha ha ha ha

But we still have a plugins property that hasn't been implemented yet. The implementation of this part is very simple, because when we use plugins, we pass an array, in which are middleware functions. The first parameter of each function is the warehouse instance itself, so we can write this in the code.

    // Processing plug-ins
    (options.plugins || []).forEach(plugin => plugin(this));

Here, our vuex is implemented. Isn't it very simple? Ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha

Here's all the code:

const forEach = (obj, cb) => {
  Object.keys(obj).forEach(key => {
    cb(key, obj[key]);
  })
}

let Vue = null;
export function install(_Vue) {
  if (Vue !== _Vue) {
    Vue = _Vue
  }
  Vue.mixin({
    beforeCreate() {
      const options = this.$options;
      if (options.store) {
        this.$store = options.store;
      } else if (options.parent && options.parent.$store) {
        this.$store = options.parent.$store;
      }
    }
  })
}

/**
 * @explain { Install module}
 *    @param { store }  Whole store 
 *    @param { rootState }  Current root state
 *    @param { path }  Created for return
 *    @param { rootModule }  Install from root module
 */

const installModule = (store, rootState, path, rootModule) => {
  if (path.length > 0) {
    // [a]
    // It's the son. The son needs to find his father and put his status on it.
    let parent = path.slice(0, -1).reduce((root, current) => {
      return root[current]
    }, rootState)
    // vue cannot add non-existent attributes on the object, otherwise it will not cause view update.
    Vue.set(parent, path[path.length - 1], rootModule.state);
    // {age:1,a:{a:1}}
    // Realize the format of finding and hanging data
  }
  // The following code is getters actions mutation in the processing module
  let getters = rootModule._rawModule.getters;
  if (getters) {
    forEach(getters, (getterName, fn) => {
      Object.defineProperty(store.getters, getterName, {
        get() {
          return fn(rootModule.state); // Let the corresponding function execute
        }
      });
    })
  }
  let mutations = rootModule._rawModule.mutations;
  if (mutations) {
    forEach(mutations, (mutationName, fn) => {
      let mutations = store.mutations[mutationName] || [];
      mutations.push((payload) => {
        fn(rootModule.state, payload);
        // Publish lets all subscriptions execute in turn
        store._subscribes.forEach(fn => fn({ type: mutationName, payload }, rootState));
      })
      store.mutations[mutationName] = mutations;
    })
  }
  let actions = rootModule._rawModule.actions;
  if (actions) {
    forEach(actions, (actionName, fn) => {
      let actions = store.actions[actionName] || [];
      actions.push((payload) => {
        fn(store, payload);
      })
      store.actions[actionName] = actions;
    })
  }
  // Mount my son
  forEach(rootModule._chidlren, (moduleName, module) => {
    installModule(store, rootState, path.concat(moduleName), module)
  })
}


class ModuleCollection { // Format
  constructor(options) {
    // Register module register module as tree structure
    this.register([], options);
  }
  register(path, rootModule) {
    let module = { // Format module
      _rawModule: rootModule,
      _chidlren: {},
      state: rootModule.state
    }
    if (path.length == 0) {
      // If it's the root module, hang the module on the root instance
      this.root = module;
    } else {
      // Recursion uses the reduce method
      // Search by the "children" attribute
      let parent = path.slice(0, -1).reduce((root, current) => {
        return root._chidlren[current]
      }, this.root)
      parent._chidlren[path[path.length - 1]] = module
    }
    // Check whether there are modules in the current module. If there are modules, start to register again.
    if (rootModule.modules) {
      forEach(rootModule.modules, (moduleName, module) => {
        this.register(path.concat(moduleName), module)
      })
    }
  }
}


export class Store {
  constructor(options = {}) {
    this.vm = new Vue({
      data(){
        return {
          state: options.state
        }
      }
    })
    
    this.getters = {};
    this.mutations = {};
    this.actions = {};
    this._subscribes = [];
    // Format the data into a desired tree structure
    this._modules = new ModuleCollection(options);

    /**
     * @explain { Install module}
     *    @param { this }  Whole store 
     *    @param { this.state }  Current root state
     *    @param { [] }  Created for return
     *    @param { this._modules.root }  Install from root module
     */
    installModule(this, this.state, [], this._modules.root);

    // Processing plug-ins
    (options.plugins || []).forEach(plugin => plugin(this));
  }

  subscribe(fn){
    this._subscribes.push(fn);
  }

  commit = (type, payload) => {
    this.mutations[type].forEach(cb => cb(payload))
  }

  dispatch = (type, payload) => {
    this.actions[type].forEach(cb => cb(payload))
  }

  get state() {
    return this.vm.state
  }

}

export default {
  install,
  Store,
}

Thank you for watching! Ha ha ha ha

Posted by einamiga on Wed, 30 Oct 2019 11:59:43 -0700