環境和測試代碼
vue 2.6.11, vuex 3.1.3
<!DOCTYPE html>
<html>
<head>
<title>vue test</title>
</head>
<body>
<div id="app">
<button @click="doAdd">do add 2</button>
{{vCount}}
<button-counter></button-counter>
</div>
<!-- Vue.js v2.6.11 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.bootcss.com/vuex/3.1.3/vuex.js"></script>
<script>
Vue.component('button-counter', {
data: function () {
return {}
},
mounted() {
console.log('child ', this.$store);
},
template: '<button v-on:click="doChildAdd">do add 5</button>',
methods: {
doChildAdd() {
store.commit('increment', 5)
}
}
});
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state, n = 1) {
state.count += n;
}
}
})
var app = new Vue({
el: '#app',
store,
computed: {
vCount () {
return this.$store.state.count
},
},
mounted() {
console.log('parent ', this.$store);
},
methods: {
doAdd() {
store.commit('increment', 2)
}
}
})
console.log(app);
// var event = new CustomEvent('test', { 'detail': 5 }); window.dispatchEvent(event);
</script>
</body>
</html>
總結
侵入每個 vue 組件注冊了 $store 屬性,而所有 $store 屬性都指向一個 store 實例,這樣就能做到所有 vue 組件訪問的都是同一份全局變量。
vue 組件里用戶定義取 store 上的變量用於渲染或者其他邏輯,而后改動 this.store.xxx 時,vue 本身核心依賴收集能知道要更新哪些視圖,就完成了。
分析
首先給出 store 的定義代碼,注意下文的 installModule 和 resetStoreVM,這是兩個關鍵的步驟
var Store = function Store (options) {
// ...
this._actions = Object.create(null);
this._actionSubscribers = [];
this._mutations = Object.create(null);
this._wrappedGetters = Object.create(null);
this._modules = new ModuleCollection(options);
this._modulesNamespaceMap = Object.create(null);
this._subscribers = [];
// bind commit and dispatch to self
var store = this;
var ref = this;
var dispatch = ref.dispatch;
var commit = ref.commit;
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
};
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
};
var state = this._modules.root.state;
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root);
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state);
// ...
};
resetStoreVM(this, state)
方法
store._vm = new Vue({
data: {
$$state: state
},
computed: computed
});
而 install(window.Vue);
方法是用來掛載 store 對象到 vue 實例的,里面做了件事 Vue.mixin({ beforeCreate: vuexInit });
vuexInit
函數里就做了把 store 復制給 vue 組件的 this.$store
屬性.
function vuexInit () {
var options = this.$options;
// store injection
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store;
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store;
}
}
commit
下文關鍵部分是 this._withCommit
里的匿名函數,它遍歷 entry,執行用戶自定義的 handler 處理函數,
就上文的測試代碼來說,這個 handler 就是 commit 里的函數 increment (state, n = 1) { state.count += n; }
接着 handler 里要讀取 state.count,就會去獲取 state,return this._vm._data.$$state
Store.prototype.commit = function commit(_type, _payload, _options) {
var this$1 = this;
// check object-style commit
var ref = unifyObjectStyle(_type, _payload, _options);
var type = ref.type;
var payload = ref.payload;
var options = ref.options;
var mutation = { type: type, payload: payload };
var entry = this._mutations[type];
this._withCommit(function() {
entry.forEach(function commitIterator(handler) {
handler(payload);
});
});
// ...
};
// _withCommit 執行它所傳入的 fn,它遍歷 entry,執行用戶自定義的 handler 處理函數,
// 這個 handler 就是我們定義的 commit 里的函數 increment (state, n = 1) { state.count += n; },總之要變動 state.count,就會進入 state 的攔截器,
prototypeAccessors$1.state.get = function () {
return this._vm._data.$$state
};
// 一旦觸發去 vue 的 _data 上有 vue 自己的攔截器 get,而動作做 state.count += n 后,就觸發了 vue 自己攔截器里的 set。最后這樣就開始vue自身的渲染邏輯。
最后修改了 state.count 后,等於說用戶也變動了在 vue 組件里那個 computed,自然而然的進入 vue 組件自身的 get/set 以及渲染邏輯。
computed: {
vCount () {
return this.$store.state.count
},
},
dispatch
dispatch 與 commit 的流程大體相同,不同點是他會使用 Promise.all 來保證 handler 函數的異步觸發,並且最后也會 return 一個 promise 對象出去而已。
Store.prototype.dispatch = function dispatch(_type, _payload) {
var this$1 = this;
// check object-style dispatch
var ref = unifyObjectStyle(_type, _payload);
var type = ref.type;
var payload = ref.payload;
var action = { type: type, payload: payload };
var entry = this._actions[type];
entry.length > 1
? Promise.all(
entry.map(function(handler) {
return handler(payload);
})
)
: entry[0](payload);
return result.then(function(res) {
return res;
});
};