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的模組化。