從實現一個簡單的 Vuex 類來了解 Vuex 的工作原理。
實現思路
Vuex 下擁有 install 方法和 Store 類。即創建一個 Vuex 的模塊,這個模塊導出 install 方法和 Store 類。
install 方法
Vuex 是 Vue 的一個插件,所以需要實現 Vue 插件約定的 install 方 法。
Vue.use 內部會調用 Vuex 對象的 install 方法。
install作用:在 install 中把創建 Vue 根實例時傳入的store
對象注入到 Vue 實例的$store
中。目的是,在所有組件中可以通過this.$store
獲取到 Vuex 中的倉庫。
然而在 install 方法中無法拿到 Vue 的實例對象(vm)。
怎么解決呢?
Vuex 中通過混入 beforeCreate 來獲取 Vue 實例。
部分代碼如下所示:
function install(Vue) {// 將Vue實例傳遞給一個局部變量,以便在函數范圍內使用_Vue = Vue;// 通過混入beforeCreate來獲取Vue實例,從而拿到選項中的store對象_Vue.mixin({beforeCreate() {if (this.$options.store) {this.$store = this.$options.store;} else if (this.$options.parent && this.$options.parent.$store) {this.$store = this.$options.parent.$store}},});
}
每個組件實例在創建之前,都會檢查是否有store選項,如果有,則將其注入到組件實例的$store
屬性中。如果沒有,它會在組件的父級鏈中查找$store
屬性。
Store 類
首先 store 是一個類,它的構造函數接受一個對象作為參數,這個對象中的屬性就是我們熟悉的 state、getters、mutations、actions。
- 實現構造函數,接收 options。
- state 的響應式處理。
- getterrs 的實現。
- commit、dispatch 方法。
注意:
下面代碼對 getters 處理中:
其中this.getters = Object.create(null)
,此處不直接寫this.getters = getters
,是因為下面的代碼中要方法 getters 中的 key 如果這么寫的話,會導致 this.getters 和 getters 指向同一個對象,當訪問 getters 的 key 的時候,實際上就是訪問 this.getters 的 key 會觸發 key 屬性的 get,會產生死遞歸。
class Store {constructor(options) {const { state = {}, getters = {}, mutations = {}, actions = {} } = options;this.state = _Vue.observable(state);// 此處不直接 this.getters = getters,會產生死遞歸this.getters = Object.create(null);Object.keys(getters).forEach((key) => {Object.defineProperty(this.getters, key, {// 箭頭函數 返回通過key在getters中獲取到的方法的執行 結果get: () => getters[key](this.state), // 這里是state如何傳到getters中的});});// mutations actions都是內部屬性,不希望外部直接訪問到this._mutations = mutations;this._actions = actions;}commit(type, payload) {this._mutations[type](this.state, payload);}dispatch(type, payload) {this._actions[type](this, payload);}
}
完整結構
Vue 中使用 Vuex 示例如下:
store–index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({state: { count: 0, msg: "Hello World" },getters: {reverseMsg(state) {return state.msg.split("").reverse().join("");},},mutations: {increate(state, payload) {state.count += payload.num;},},actions: {increate(context, payload) {setTimeout(() => {context.commit("increate", { num: 5 });}, 2000);},},
});
myvuex–index.js
let _Vue = null;class Store {constructor(options) {const { state = {}, getters = {}, mutations = {}, actions = {} } = options;this.state = _Vue.observable(state);// 此處不直接 this.getters = getters,會產生死遞歸this.getters = Object.create(null);Object.keys(getters).forEach((key) => {Object.defineProperty(this.getters, key, {// 箭頭函數 返回通過key在getters中獲取到的方法的執行 結果get: () => getters[key](this.state), // 這里是state如何傳到getters中的});});// mutations actions都是內部屬性,不希望外部直接訪問到this._mutations = mutations;this._actions = actions;}commit(type, payload) {this._mutations[type](this.state, payload);}dispatch(type, payload) {this._actions[type](this, payload);}
}function install(Vue) {// 將Vue實例傳遞給一個局部變量,以便在函數范圍內使用_Vue = Vue;// 通過混入beforeCreate來獲取Vue實例,從而拿到選項中的store對象_Vue.mixin({beforeCreate() {if (this.$options.store) {this.$store = this.$options.store;} else if (this.$options.parent && this.$options.parent.$store) {this.$store = this.$options.parent.$store}},});
}// 導出模塊
export default {Store,install,
};