## 為什么要使用 Vuex
當我們使用 Vue.js 來開發一個單頁應用時,經常會遇到一些組件間共享的數據或狀態,或是需要通過 props 深層傳遞的一些數據。在應用規模較小的時候,我們會使用 props、事件等常用的父子組件的組件間通信方法,或者是通過事件總線來進行任意兩個組件的通信。但是當應用逐漸復雜后,問題就開始出現了,這樣的通信方式會導致數據流異常地混亂。

這個時候,我們就需要用到我們的狀態管理工具 Vuex 了。Vuex 是一個專門為 Vue.js 框架設計的、專門用來對于 Vue.js 應用進行狀態管理的庫。它借鑒了 Flux、redux 的基本思想,將狀態抽離到全局,形成一個 Store。因為 Vuex 內部采用了 new Vue 來將 Store 內的數據進行「響應式化」,所以 Vuex 是一款利用 Vue 內部機制的庫,與 Vue 高度契合,與 Vue 搭配使用顯得更加簡單高效,但缺點是不能與其他的框架(如 react)配合使用。
本節將簡單介紹 Vuex 最核心的內部機制,起個拋磚引玉的作用,想了解更多細節可以參考筆者 [Github](https://github.com/answershuto) 上的另一篇文章 [《Vuex源碼解析》](https://github.com/answershuto/learnVue/blob/master/docs/Vuex%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.MarkDown)或者直接閱讀 [Vuex源碼](https://github.com/vuejs/vuex)。
## 安裝
Vue.js 提供了一個 `Vue.use` 的方法來安裝插件,內部會調用插件提供的 `install` 方法。
```
Vue.use(Vuex);
```
所以我們的插件需要提供一個 `install` 方法來安裝。
```
let Vue;
export default install (_Vue) {
Vue.mixin({ beforeCreate: vuexInit });
Vue = _Vue;
}
```
我們采用 `Vue.mixin` 方法將 `vuexInit` 方法混淆進 `beforeCreate` 鉤子中,并用 `Vue` 保存 Vue 對象。那么 `vuexInit` 究竟實現了什么呢?
我們知道,在使用 Vuex 的時候,我們需要將 `store` 傳入到 Vue 實例中去。
```
/*將store放入Vue創建時的option中*/
new Vue({
el: '#app',
store
});
```
但是我們卻在每一個 vm 中都可以訪問該 `store`,這個就需要靠 `vuexInit` 了。
```
function vuexInit () {
const options = this.$options;
if (options.store) {
this.$store = options.store;
} else {
this.$store = options.parent.$store;
}
}
```
因為之前已經用`Vue.mixin` 方法將 `vuexInit` 方法混淆進 `beforeCreate` 鉤子中,所以每一個 vm 實例都會調用 `vuexInit` 方法。
如果是根節點(`$options`中存在 `store` 說明是根節點),則直接將 `options.store` 賦值給 `this.$store`。否則則說明不是根節點,從父節點的 `$store` 中獲取。
通過這步的操作,我們已經可以在任意一個 vm 中通過 `this.$store` 來訪問 `Store` 的實例啦~
## Store
### 數據的響應式化
首先我們需要在 `Store` 的構造函數中對 `state` 進行「響應式化」。
```
constructor () {
this._vm = new Vue({
data: {
$$state: this.state
}
})
}
```
熟悉「響應式」的同學肯定知道,這個步驟以后,`state` 會將需要的依賴收集在 `Dep` 中,在被修改時更新對應視圖。我們來看一個小例子。
```
let globalData = {
d: 'hello world'
};
new Vue({
data () {
return {
$$state: {
globalData
}
}
}
});
/* modify */
setTimeout(() => {
globalData.d = 'hi~';
}, 1000);
Vue.prototype.globalData = globalData;
```
任意模板中
```
<div>{{globalData.d}}</div>
```
上述代碼在全局有一個 `globalData`,它被傳入一個 `Vue` 對象的 `data` 中,之后在任意 Vue 模板中對該變量進行展示,因為此時 `globalData` 已經在 Vue 的 `prototype` 上了所以直接通過 `this.prototype` 訪問,也就是在模板中的 `{{globalData.d}}`。此時,`setTimeout` 在 1s 之后將 `globalData.d` 進行修改,我們發現模板中的 `globalData.d` 發生了變化。其實上述部分就是 Vuex 依賴 Vue 核心實現數據的“響應式化”。
講完了 Vuex 最核心的通過 Vue 進行數據的「響應式化」,接下來我們再來介紹兩個 `Store` 的 API。
### commit
首先是 `commit` 方法,我們知道 `commit` 方法是用來觸發 `mutation` 的。
```
commit (type, payload, _options) {
const entry = this._mutations[type];
entry.forEach(function commitIterator (handler) {
handler(payload);
});
}
```
從 `_mutations` 中取出對應的 mutation,循環執行其中的每一個 mutation。
### dispatch
`dispatch` 同樣道理,用于觸發 action,可以包含異步狀態。
```
dispatch (type, payload) {
const entry = this._actions[type];
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload);
}
```
同樣的,取出 `_actions` 中的所有對應 action,將其執行,如果有多個則用 `Promise.all` 進行包裝。
## 最后
理解 Vuex 的核心在于理解其如何與 Vue 本身結合,如何利用 Vue 的響應式機制來實現核心 Store 的「響應式化」。
Vuex 本身代碼不多且設計優雅,非常值得一讀,想閱讀源碼的同學請看[Vuex源碼](https://github.com/vuejs/vuex)。
注:本節代碼參考[《Vuex狀態管理的工作原理》](https://github.com/answershuto/VueDemo/blob/master/%E3%80%8AVuex%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E3%80%8B.js)。