前幾天有盆友在群裡問,vue.$emit和$on可以跨元件完成全域性通訊,那豈不是可以完全代替vuex,為啥還要用vuex呢。
這個問題就要從eventbu事件匯流排s和vuex起源說起了。
(趁著尤大的vue3.0還在路上,我們來炒炒現飯)
元件通訊
在很久很久以前,在Vue王國裡有一個元件塔,有一個元件家族,A,B,C,元件父親A和兒子B,C生活在不同的層級之間,他們相隔非常遠。
父->子
這個時候,父親想念兒子了,想給他寄信,怎麼辦呢?簡單!在每一個子元件上都有一個props “郵箱”,通過這個郵箱,父親可以直接投遞郵件(data)
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="我是父親的訊息!"></blog-post>
複製程式碼
子->父
收到父親來信後,B,C都非常高興。想給父親回話,這時白鴿使者vue.$emit出來了,子元件用vue.$emit傳遞訊息,往上層傳播。
this.$emit("todo",{
res:"我是兒子的訊息"
})
複製程式碼
接到訊息後,父親得對兒子傳遞過來的不同型別資訊做出不同的迴應
this.$on('todo',function(data){
// todo
//data => 來自兒子的訊息
})
複製程式碼
兄弟
A經常教育B和C,說兄弟之間要多聯絡,增加感情,但是$emit又只能向上層傳遞訊息,住在同一層的兄弟兩又該怎麼聯絡呢?
eventbu事件匯流排
這時候有一個智者想到了一個方法,既然你們都用$emit這個方法,我何不直接開個順豐快遞呢? 這時候vue eventbus事件匯流排出來了,利用一個遊離在元件塔外的vue例項,構造了一個bus匯流排,兒子的訊息通過$emit給bus匯流排.
B.$root.bus.$emit('todo')
複製程式碼
這時候父親所得到的訊息是由bus匯流排發出的,並設定響應事件
A.$root.bus.on('todo',function(){})
複製程式碼
這樣父親兒子就可以輕輕鬆鬆的聯絡了
那最關鍵的問題是,兄弟之間怎麼聯絡呢?
匯流排的智者這時候跳出來說,顧客就是上帝,不管是父親兒子都可以使用我們順豐快遞匯流排,這樣也就實現了兄弟元件之間的通訊
B.$root.bus.$emit('todo')
// 兄弟C也能收到這個訊息
C.$root.bus.on('todo',function(){})
複製程式碼
problem?
這個時候回到本文關鍵,為什麼eventBus這麼好的東西我們還要使用vuex呢?
以下內容參照 vuex-basics-tutorial
最開始,我們的專案可能是簡簡單單這樣的
1. 程式碼邏輯性極具下降,可閱讀性變低
2. 對於每一個action父元件都需要一個on(或dispatch)一個事件來處理
3. 你將很難查詢到每一個事件是從哪裡觸發,滿篇都是業務邏輯
複製程式碼
簡單的例子
舉個簡單的例子。你的公司原來有一個display元件,他的作用是展現App根元件上increment的當前值。你新招了兩個新員工,給他們分配了兩個任務。
- 你讓小A開發一個新的計數器在一個新的元件(childDisplay)裡運用,實現點選一次增加increment一次,並在元件內展現increment當前的數字。這個計數器訂閱了increment,點選計數器成功讓display和childDisplay顯示increment增加1後的數字。
小A開開心心迅速的完成了這個任務commit然後push了 - 你跟小B說,我需要一個按鈕元件,這個按鈕元件向app例項emit了一個reset事件,這個事件將重置App上increment為0
好的,小B也開開心心的commit然後push了 - 這個時候你會發現當你點選A元件時,在display和childDiplay上都成功顯示了increment增加1以後的數字,但點選B元件觸發reset事件時,根元件上的increment重置為了0,但由於小B並不知道A元件也訂閱了increment這個資料,導致A元件狀態沒有更新。
vuex
為了防止這些問題,這個時候,國王尤小右站了出來,他藉助隔壁flux王國的思想,帶領全國的智者研究出了一個新的方式來解決這種混亂的問題。那就是vuex了。
按照定義來說
- store是一個倉庫,倉庫儲存著專案的資料(store.state)
- (嚴格模式)為了解決無法追蹤狀態變化的問題,倉庫裡的資料不直接通過修改store.state來改變,而是通過commit一個動作告訴倉庫,將會報錯 [vuex] Do not mutate vuex store state outside mutation handlers
- 倉庫收到了commit的動作後,將會在store.mutation裡查詢對應事件,並改變store.state
回到簡單的例子
解決方案1(簡單store模式)
為了解決上述問題,我們做出了幾個有意思的改變。(state)
- 在store裡定義了一個常量store
- 在本地資料裡定義了一個叫做sharedState的變數,是store.state裡的對映
- 因為sharedState是vue的資料,所以他讓store.state也變成了一個響應式資料,當store.state裡資料發生改變時,vue將會自動的更新sharedState裡的資料。
這樣,原有的問題就解決了,A元件和B元件訂閱的increment不再是根元件上的資料了,而是倉庫的資料。當倉庫資料改變時,vue將自動更新到每個訂閱了increment資料的元件上。
// 這是父級display元件
<template>
Count is {{ sharedState.counter }}
</template>
<script>
import store from '../store'
export default {
data () {
return {
sharedState: store.state
}
}
}
</script>
複製程式碼
// 這是A元件
import store from '../store'
<template>
Count is {{ sharedState.counter }}
</template>
export default {
data () {
return {
sharedState: store.state
}
},
methods: {
activate () {
this.sharedState.counter += 1
}
}
}
複製程式碼
// 這是B元件
import store from '../store'
export default {
data () {
return {
sharedState: store.state
}
},
methods: {
reset () {
this.sharedState.counter = 0
}
}
}
複製程式碼
方案1變形
上述的方式就是最好的了嗎?試想當A,B在這家公司開發了無數多個reset元件和計數器元件後離職了。這時候招來了一個新的員工C,你跟C說,我想要所有的increment大小不超過100,難道C要對所有元件進行重構,判斷大小嗎,這是非常荒謬的選擇。這時候我們引入了新的方法。(mutation)
var store = {
state: {
counter: 0
},
increment: function () {
if (store.state.counter < 100) {
store.state.counter += 1;
}
},
reset: function () {
store.state.counter = 0;
}
}
export default store
複製程式碼
根據上面的衍生,vuex就慢慢成型了
我們重新建立store.jsimport Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
var store = new Vuex.Store({
state: {
counter: 0
},
mutations: {
INCREMENT (state) {
state.counter ++
}
}
})
export default store
複製程式碼
我們看下這裡做了什麼樣的操作
- 我們引入了一個vuex模組,並作為vue外掛啟用
- 我們的倉庫store不再是一個普通的jsonObject而是一個vuex.store的例項
- 採用了mutation,用於改變state
why better ?
- 如果在開發過程中保留了所有狀態的副本,我們可以像super hero一樣如時空穿梭般對程式碼進行除錯,記錄每一次狀態的變化
- 你可以構建一箇中介軟體,比如一個日誌列印器,在每次使用者提交一個action時記錄操作,當出現問題時你將會更加容易的除錯與fix bug
- 強制要求你把所有action通過一個store管理,這樣團隊開發將會更加便捷與明瞭
總結
通過長篇的分析我們可以得出本文結論了。
- vuex是官方推出的,事件匯流排是高手在民間
- 在大型應用方面,vuex確實是一個比EventBus更好的解決方案
- vuex更加易於除錯與管理
- Vuex並不是最佳的解決方案,在某些小型應用上,你可能只有小部分的資料互動,甚至只有一個登入狀態儲存,那樣事件匯流排或者簡單狀態管理都是值得推薦的。
最後說一句,vuex真香。 ps:這只是筆者的見解,有什麼問題歡迎大家踴躍提出指正。?