vuex的實現

  • 2019 年 10 月 3 日
  • 筆記

源程式碼可以查看我的github:  https://github.com/Jasonwang911/TryHardEveryDay/tree/master/Vuex/vuex-resouce  歡迎star

 

先描述一下vuex的使用:

store.js

import Vue from "vue";  import Vuex from "./vuex/myVuex";    Vue.use(Vuex);    export default new Vuex.Store({    state: {      age: 20    },    getters: {      // date 的 computed      myAge(state) {        return state.age + 10;      }    },    mutations: {      syncAdd(state, payload) {        state.age += payload;      },      syncMinuts(state, payload) {        state.age -= payload;      }    },    actions: {      asyncMinus({ commit }, payload) {        setTimeout(() => {          commit("syncMinuts", payload);        }, 1000);      }    }  });  

  

main.js中引用:

import Vue from "vue";  import App from "./App.vue";  import router from "./router";  import store from "./store";    Vue.config.productionTip = false;    new Vue({    router,    store, // 在每個實例上添加一個 $store 對象    render: h => h(App)  }).$mount("#app");  

 

先說這兩點 

1. Store是一個類

2. Vue.use(Vuex);  

 

首先建立一個類並導出,同時導出一個install的方法,這個方法接收一個參數 _Vue,即 vue實例

let Vue    class Store {    }    const install = _Vue => {    Vue = _Vue  }    export default {    Store,    install  }  

  

然後再main.js 中所作的就是講store實例註冊到vue的跟組件和所有的子組件上, 這裡使用了Vue.mixin()的方法,在每個組件中注入$store,並在子組件中通過 this.$parents來拿到父組件的$store並添加到子組件的實例上

const install = _Vue => {    Vue = _Vue;    // console.log(Vue);    Vue.mixin({      beforeCreate() {        if (this.$options && this.$options.store) {          // 根組件          this.$store = this.$options.store;        } else {          this.$store = this.$parent && this.$parent.$store;        }      }    });  };  

  

先來看看state是怎麼用的

import Vue from "vue";  import Vuex from "./vuex/myVuex";    Vue.use(Vuex);    export default new Vuex.Store({    state: {      age: 20    },  });

 

在new Vuex.Store的實例的時候傳入了一個對象,這個對象options

然後我們添加state的這個屬性

class Store {    constructor(options) {      // options 是 new Vuex時注入的對象      this._vm = options.state || {};    }    get state() {      // store.state      return this._vm;    }  }

這裡有一個問題,通過options獲取的state並不是Observe狀態的,並不能響應式的更新視圖,如何做才能使state變成響應式的並觸發視圖的更新呢,也就是通過Vue的數據劫持進行包裝轉換,

這裡通過new Vue,並把state放入創建的Vue的data中來實現這個操作

 

class Store {    constructor(options) {      // options 是 new Vuex時注入的對象      this._vm = new Vue({        data: {          state: options.state        }      });    }    get state() {      // store.state      return this._vm.state;    }  }  

 

這樣,$store.state就變成了響應式的狀態了

 

接下來,同理  getters,mutations, actions 也是通過options傳入獲取的

import Vue from "vue";  import Vuex from "./vuex/myVuex";    Vue.use(Vuex);    export default new Vuex.Store({    state: {      age: 20    },    getters: {      // date 的 computed      myAge(state) {        return state.age + 10;      }    },    mutations: {      syncAdd(state, payload) {        state.age += payload;      },      syncMinuts(state, payload) {        state.age -= payload;      }    },    actions: {      asyncMinus({ commit }, payload) {        setTimeout(() => {          commit("syncMinuts", payload);        }, 1000);      }    }  });  

  

先來實現一下getters方法: 每一個getter方法其實都是getters的一個屬性,但是這個屬性返回了一個函數。屬性可以返回函數的方法可以通過Object.definedProperty()中的get()來實現:

class Store {    constructor(options) {      // options 是 new Vuex.Store時注入的對象      // getters 拿到new Store的時候傳入的getters      let getters = options.getters || {};      // 創建 Store 的 getters 屬性      this.getters = {};      Object.keys(getters).forEach(getterName => {        // 是個函數,但是返回的是個屬性        Object.defineProperty(this.getters, getterName, {          get() {            return getters[getterName](this.state);          }        });      });    }    }  

  

這裡其實只做了一件事情: 循環傳入的options上的getters對象,並在用戶調用getters中的每一個屬性的時候返回一個getter函數並執行,

同理,mutations和actions也是如此實現的,只不過各自多了一個commit和dispatch的方法。

commit 和 dispatch 方法的原理就是通過參數type去對應的mutations和actions中查找對應的mutation或者action傳入payload並執行

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

  

查看Vuex源碼,作者巧妙的將這個通過對象key進行循環的方法封裝成了一個forEach函數:

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

 這個函數傳入了一個對象和一個回調函數,通過對key的循環,執行了回調函數,並向回調函數中傳入了循環出對象obj的key和value,整理後整個Store對象變成了如下:

const forEach = (obj, callback) => {    Object.keys(obj).forEach(key => {      callback(key, obj[key]);    });  };    class Store {    constructor(options) {      // options 是 new Vuex時注入的對象      // this._s = options.state || {};      // 將store中的狀態變成可被觀測的數據      this._vm = new Vue({        data: {          state: options.state        }      });      // getters 拿到new Store的時候傳入的getters      let getters = options.getters || {};      // 創建 Store 的 getters 屬性      this.getters = {};      forEach(getters, (getterName, fn) => {        Object.defineProperty(this.getters, getterName, {          get: () => {            return fn(this.state);          }        });      });      // mutations      let mutations = options.mutations || {};      this.mutations = {};      forEach(mutations, (mutationName, fn) => {        this.mutations[mutationName] = payload => {          fn(this.state, payload);        };      });      // actions      let actions = options.actions || {};      this.actions = {};      forEach(actions, (actionName, fn) => {        this.actions[actionName] = payload => {           fn(this, payload);        };      });    }    dispatch = (type, payload) => {      this.actions[type](payload);    };    commit = (type, payload) => {      this.mutations[type](payload);    };    get state() {      return this._vm.state;    }  }  

 

以上基本實現了一個簡單的vuex的基本流程,所有的源碼可以在我的github上查看,後續會繼續修改支援vuex的模組化。