Vuex has come out with a version of the cameo version of Vue3, and while it still knows how to use the gadget, we have to catch its tail and create a mini Vuex which has a cameo effect on ㅂ•́)و✧

Directory structure and introduction

├─ MiniVuex ├─ Applymixin.js provides store on Vue instances to import ├─ Help.js provides support functions ├─ index.js main portal ├─ store.js store class ├─ Utils.js provides utility methodsCopy the code

After understanding about the catalog and function we began to our usual operation process respectively on the corresponding source code implementation.

MiniVuex.Store

MiniVuex/ index.js

Let’s release our main entry file and implement each of these methods.

import { Store, install } from './store';

import { mapState, mapGetters, mapMutations, mapActions } from './helper';

export default { install, Store };
export { mapState, mapGetters, mapMutations, mapActions };
Copy the code

First, install MiniVuex into Vue and inject the classes returned after MiniVuex.Store is created into the Vue instance.

Install MiniVuex and create a Store instance

src/store.js

import Vue from 'vue'
import MiniVuex from './MiniVuex'

// Install Vuex
Vue.use(MiniVuex)

// Create and export the store object. For convenience, do not set the parameters
export default new MiniVuex.Store()
Copy the code

src/main.js

import Vue from 'vue';
import App from './App.vue';
import store from './store';

Vue.config.productionTip = false;

new Vue({
  store, // Pass store into the Vue configuration item
  render: h= > h(App)
}).$mount('#app');
Copy the code

The above is the basic use of MiniVuex, next we will analyze and implement the above code.

install

First use vue. use to install the plug-in is to call the corresponding plug-in install method, the following is the source of vue. use.

Vue.use = function (plugin) {
      var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
      if (installedPlugins.indexOf(plugin) > -1) {
        return this
      }

      // additional parameters
      var args = toArray(arguments.1);
      args.unshift(this);
      if (typeof plugin.install === 'function') {
        plugin.install.apply(plugin, args);
      } else if (typeof plugin === 'function') {
        plugin.apply(null, args);
      }
      installedPlugins.push(plugin);
      return this
    };
Copy the code

Use: The Vue will execute the install method of the plug-in and pass itself (Vue) as a parameter to the plug-in. The plugin can then inject the incoming Vue

So let’s start implementing install for the plug-in.

MiniVuex/store.js

import applyMixin from './applyMixin';

// Keep the Vue constructor
let Vue;

class Store {}

/ / Vuex installation
const install = _Vue= > {
  // Save the Vue constructor
  applyMixin((Vue = _Vue));
};

export { Store, install };
Copy the code

MiniVuex/applyMixin.js

// Inject Store into the component
export default function applyMixin(Vue) {
  // Check the Vue version. Only the V2 version is available
  let version = Vue.version.split('. ') [0];

  if (version == 2) {
    // Match the version
    // Blend the Vuex initialization function into each component's beforeCreate life cycle
    Vue.mixin({
      beforeCreate: VuexInit
    });
  } else {
    console.log('(get out of here) your version too${version >= 3 ? 'high' : 'low'}The `); }}/ / initialization
export function VuexInit() {
  var options = this.$options;
  // Set the initialization root component's store to the component's $store property
  // Check whether the root component has injected store
  // Since we are using vue. mixin for store injection, Vue does the recursive processing internally, so we don't need to worry about the recursive implementation
  if (options.store) {
    this.$store = options.store;
  } else if (options.parent && options.parent.$store) {
    // The child component takes the parent component's $store property and sets it layer by layer
    this.$store = options.parent.$store; }}Copy the code

If you don’t know the configuration parameters required by Vuex.Store state constructor, please read the official documentation first.

We are the Mini version of Vuex, so all configurations will not be realized. We only need to support common configuration items, mainly state, getters, mutations, Actions and modules.

state

Process each configuration item one by one. So the first thing we do when we create the store object is we define the state object to initialize it so we define the state value of our store.

MiniVuex/utils.js

Define object traversal functions

export const forEachObj = (obj, fn) = > {
  for (var key inobj) { fn && fn(key, obj[key]); }};Copy the code

MiniVuex/store.js

import applyMixin from './applyMixin';
import { forEachObj } from './utils';

// Keep the Vue constructor
let Vue;

// Protect the Vue instance from being modified by injection
const VM = Symbol('VM');

class Store {
  constructor({ state, ... options }) {
    // Use Vue to monitor the status and realize the real-time change of the view
    this[VM] = new Vue({
      data: {
        /** * why not {... State} or {data: state}? * Since state may contain attributes beginning with _ or $, and attributes directly starting with _ or $in data are not propped up by Vue instances and cannot be obtained by vm[property], * Because they may conflict with Vue's built-in property and API methods. * For the sake of uniform access, we set them in a property called data, which can be accessed directly later. * * /
        $$state: state
      }
    });
  }

  // We can get the set of states from store. State, so we use getters to listen for state and return data
  get state() {
    return this[VM]._data.$$state; }}/ / Vuex installation
const install = _Vue= > {
  // Save the Vue constructor
  applyMixin((Vue = _Vue));
};

export { Store, install };
Copy the code

getters

Vuex allows us to define “getters” (you can think of them as computed properties of the store) in the store. Just like evaluating properties, the return value of a getter is cached based on its dependency and is recalculated only if its dependency value changes. – Vuex document

Therefore, we can also use computed data of Vue to monitor and cache the getters we set. Let’s Coding~

/ /...
class Store{
    constructor({state,getters = {},... options}){
        / /...
        
        const computed = {};
        this.getters = {};
        
        // Iterate over getters, encapsulating its value into a new object and assigning it to Vue's computed cache
        forEachObj(getters, (key, gets) = > {
          computed[key] = () = > gets(this.state);
          
          // Make sure we keep the value up to date every time
          Object.defineProperty(this.getters, key, {
            get: () = > this[VM][key]
          });
        });
    
        this[VM] = new Vue({
          data: {
            $$state: state
          },
          // Pass computed configuration items to Vue
          computed: computed
        });
    }
    / /...
}
/ /...
Copy the code

mutations

Steal a lazy, paste official instructions (; ´ del `) y – ~ ~

The only way to change the state in Vuex’s store is to commit mutation. Mutations in Vuex are very similar to events: each mutation has a string event type (type) and a callback function (handler). This callback is where we actually make the state change, and it accepts state as the first argument.

For example, start by creating a store that includes mutations.

usage

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    add (state,payload) {
      // Change the state
      state.count += payload
    }
  }
})
Copy the code

If Wimbledon wants to update the count, we need to do this:

store.commit('add'.1); // state.count is updated to 2
Copy the code

General usage basic understanding, sa, ha zi sesame oil ~

MiniVuex/store.js

// ..

class Store{
    constructor({state,getters = {},mutations = {},... options}){
    / /...
    
    // this redirects
    this.commit = this.commit.bind(this);
    
    this.mutations = mutations;
    / /...
    }
    
    // Submit to mutations for data changes
    commit(key, ... payload) {
        if(this.mutations[key] instanceof Function) {this.mutations[key](this.state, ... payload); }else{
            console.log(` [MiniVuex] : the mutations${mutations}Is not a method)}}/ /...
}
// ..
Copy the code

action

Action is similar to mutation, except that:

  • The Action commits mutation rather than a direct state change.
  • Actions can contain any asynchronous operation.

The Action function accepts a context object with the same methods and properties as the store instance, so you can submit a mutation by calling context.mit. Or get state and getters via context.state and context.getters.

Continue to use the store above

usage

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    add (state,payload) {
      // Change the state
      state.count += payload
    }
  },
  actions: {
    addAsync (context) {
        // You can asynchronously request data in actions, and send ** one or more ** to the server for status updates when the server responds
        axios.then((res) = > {
            context.commit('add',res? .data? .count ||0)
        }).catch(err= > {
            console.log(err)
        })
    }
  }
})
Copy the code

Distributed update

store.dispatch('addAsync')
Copy the code

MiniVuex/store.js

/ /...
class Store{
    constructor({state, getters = {}, mutations = {}, actions = {}, ... options}){
        // Some code is omitted
        
        // this redirects
        this.dispatch = this.dispatch.bind(this);
        
        this.actions = actions;
        
    }
    
    // Data changes can be made via commit to mutations
    dispatch(key, ... payload) {
        // Pass the Store instance to the corresponding actions function
        this.actions[key](this. payload); }// Some code is omitted
}
/ /...
Copy the code

modules

Modules are also part of the Vuex core, with each module having its own state, mutation, action, getter, and even nested submodules — split the same way from top to bottom.

My approach here is a little different from Vuex in that I store each module instance directly in a Map and mark them with a specific hierarchy for easy management or manipulation.

// Omit some code

const VM = Symbol('VM');

const nameSpace = new Map(a);class Store {
  constructor({ state, getters = {}, mutations = {}, actions = {}, modules = {}, ... options }) {
    
    // Omit some code
    
    // Check whether the nameSpace has stored instances before, if not, the root module
    
    if(! nameSpace.size) {// Mark modules and store them in a nameSpace
        this[NS] = 'root';
        options[NS] = 'root';
        nameSpace.set('root'.this);
    }
    
    // Determine whether modules is set
    if (Object.keys(modules).length > 0) {
      forEachObj(modules, function(key, subOption) {
        // Create a new store and pass the parent module id to the child module for recursive id
        let instance = new Store(
          Object.assign(subOption, { [NS]: `${options[NS]}/${key}`}));// Marks the current module
        instance[NS] = `${options[NS]}/${key}`;
        
        nameSpace.set(instance[NS], instance);
        
        // Inject the current module's state into the parent state
        state[key] = instance.state;
      });
    }
    
    // Omit some code
  }

    // Omit some code
}

// omit some code load instances

Copy the code

Although not as complete and powerful as Vuex module system, but our focus is on the Mini. (mainly because I’m too tasty (╥╯^╰╥))

Check out our full example

Define the Modules

Store object

Full code pro stamp here

Auxiliary function

MiniVuex pre-packages mapState, mapGetters, mapActions, and mapMutations, which are not familiar with the use of auxiliary methods.

Helper methods can pull corresponding properties from the store, and also support module lookup, value filtering, and changing the name of the value.

Value filtering and changing the names of values can be handled by defining functions in utils.js.

utils.js

Because attribute filtering, if it is string[], simply takes the value and returns it, whereas if it is object[], it can change the value. If it is an object, it can change the attribute name, for example {count: ‘newCount’}, that is, the fetched attribute can be accessed with nnewCount to avoid duplicate name conflicts caused by too many attributes.

So we can encapsulate a function to characterize these cases.

// Encapsulates an object or array
export const normalizeMap = map= > {
  return Array.isArray(map)
    ? map.map(key= > ({ key, val: key }))
    : Object.keys(map).map(key= > ({ key, val: map[key] }));
};
Copy the code

If the transfer is [‘ count ‘] returns [{key: ‘count’, val: ‘count’}].

Return if {count: state => state.count, user: “userInfo”}

[{key: 'count'.val: state= > state.count
    },
    {
        key: 'user'.val: 'userInfo'}]Copy the code

Then we need to use the following tools to assist in development when we package the helper methods.

// Determine if the object is traversable
export const isValidMap = function isValidMap(map) {
  return Array.isArray(map) || isObject(map);
};
Copy the code
Check whether it is an objectexport const isObject = function isObject(obj) {
  returnobj ! = =null && typeof obj === 'object';
};
Copy the code

mapState

MiniVuex/helper.js

import { normalizeMap, isValidMap } from './utils';

const mapState = function(. arg) {
  let namespace = 'root',
    filters = [],
    res = {};

  if (arg.length === 1 && isValidMap(arg[0])) {
    filters = arg[0];
  } else if (
    arg.length >= 2 &&
    typeof arg[0= = ='string' &&
    isValidMap(arg[1])
  ) {
    namespace = `${namespace}/${arg[0]}`;
    filters = arg[1];
  } else {
    console.warn('[Vuex]: Parameter abnormal oh oh oh ~');
    return res;
  }

  {[key]: mappedState} {[key]: mappedState} {[key]: mappedState} {[key]: mappedState
  normalizeMap(filters).forEach(({ key, val }) = > {
    res[key] = function mappedState() {
      return typeof val === 'function'
        ? val.call(this, nameSpace.get(namespace).state)
        : nameSpace.get(namespace).state[val];
    };
  });

  return res;
};
Copy the code

The other three helper methods all do the same, so I won’t go into details.

mapGetters

const mapGetters = function(. arg) {
  let namespace = 'root',
    filters = [],
    res = {};

  if (arg.length === 1 && isValidMap(arg[0])) {
    filters = arg[0];
  } else if (
    arg.length >= 2 &&
    typeof arg[0= = ='string' &&
    isValidMap(arg[1])
  ) {
    namespace = `${namespace}/${arg[0]}`;
    filters = arg[1];
  } else {
    console.warn('[Vuex]: Parameter abnormal oh oh oh ~');
    return res;
  }

  normalizeMap(filters).forEach(({ key, val }) = > {
    res[key] = function mappedGetter() {
      return typeof val === 'function'
        ? val.call(this, nameSpace.get(namespace).getters)
        : nameSpace.get(namespace).getters[val];
    };
  });

  return res;
};
Copy the code

mapMutations

const mapMutations = function(. arg) {
  let namespace = 'root',
    filters = [],
    res = {};

  if (arg.length === 1 && isValidMap(arg[0])) {
    filters = arg[0];
  } else if (
    arg.length >= 2 &&
    typeof arg[0= = ='string' &&
    isValidMap(arg[1])
  ) {
    namespace = `${namespace}/${arg[0]}`;
    filters = arg[1];
  } else {
    console.warn('[Vuex]: Parameter abnormal oh oh oh ~');
    return res;
  }

  normalizeMap(filters).forEach(({ key, val }) = > {
    res[key] = function mappedMutation(. args) {
      console.log(... args, nameSpace.get(namespace).commit(val, ... args)); }; });return res;
};

Copy the code

mapActions

const mapActions = function(. arg) {
  let namespace = 'root',
    filters = [],
    res = {};

  if (arg.length === 1 && isValidMap(arg[0])) {
    filters = arg[0];
  } else if (
    arg.length >= 2 &&
    typeof arg[0= = ='string' &&
    isValidMap(arg[1])
  ) {
    namespace = `${namespace}/${arg[0]}`;
    filters = arg[1];
  } else {
    console.warn('[Vuex]: Parameter abnormal oh oh oh ~');
    return res;
  }

  normalizeMap(filters).forEach(({ key, val }) = > {
    res[key] = function mappedMutation(. args) {
      console.log(... args, nameSpace.get(namespace).dispatch(val, ... args)); }; });return res;
};
Copy the code

That’s all the MiniVuex realized, it’s not easy to create, I hope you can offer your precious praise, don’t have to σ (o゚д゚o Blue)

MiniVuex features:

  1. Support for creating stores
  2. Supports modules modular storage
  3. Support mapState, mapGetters, mapMutations, mapActions, using the same method as the official Vuex, support modularization.

For details, please check the source code and upload it to Github.