EventBus & Vuex?

atheist1發表於2018-12-07

前幾天有盆友在群裡問,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
最開始,我們的專案可能是簡簡單單這樣的

EventBus & Vuex?
在過了很長一段時間之後,王國不斷髮展,專案越來越大,元件塔裡的元件越來越多,他們長得也越來越複雜,整個王國變得亂七八糟,專案結構變成下面這個樣子。不同元件之間通訊越來越多,程式碼段裡不斷的出現$emit和$on導致了以下幾個主要問題

1. 程式碼邏輯性極具下降,可閱讀性變低
2. 對於每一個action父元件都需要一個on(或dispatch)一個事件來處理
3. 你將很難查詢到每一個事件是從哪裡觸發,滿篇都是業務邏輯
複製程式碼

EventBus & Vuex?

簡單的例子

舉個簡單的例子。你的公司原來有一個display元件,他的作用是展現App根元件上increment的當前值。你新招了兩個新員工,給他們分配了兩個任務。

  1. 你讓小A開發一個新的計數器在一個新的元件(childDisplay)裡運用,實現點選一次增加increment一次,並在元件內展現increment當前的數字。這個計數器訂閱了increment,點選計數器成功讓display和childDisplay顯示increment增加1後的數字。
    小A開開心心迅速的完成了這個任務commit然後push了
  2. 你跟小B說,我需要一個按鈕元件,這個按鈕元件向app例項emit了一個reset事件,這個事件將重置App上increment為0
    好的,小B也開開心心的commit然後push了
  3. 這個時候你會發現當你點選A元件時,在display和childDiplay上都成功顯示了increment增加1以後的數字,但點選B元件觸發reset事件時,根元件上的increment重置為了0,但由於小B並不知道A元件也訂閱了increment這個資料,導致A元件狀態沒有更新。

vuex

為了防止這些問題,這個時候,國王尤小右站了出來,他藉助隔壁flux王國的思想,帶領全國的智者研究出了一個新的方式來解決這種混亂的問題。那就是vuex了。

按照定義來說

  1. store是一個倉庫,倉庫儲存著專案的資料(store.state)
  2. (嚴格模式)為了解決無法追蹤狀態變化的問題,倉庫裡的資料不直接通過修改store.state來改變,而是通過commit一個動作告訴倉庫,將會報錯 [vuex] Do not mutate vuex store state outside mutation handlers
  3. 倉庫收到了commit的動作後,將會在store.mutation裡查詢對應事件,並改變store.state

回到簡單的例子

解決方案1(簡單store模式)

為了解決上述問題,我們做出了幾個有意思的改變。(state)

  1. 在store裡定義了一個常量store
  2. 在本地資料裡定義了一個叫做sharedState的變數,是store.state裡的對映
  3. 因為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就慢慢成型了

vuex
我們重新建立store.js

import 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
複製程式碼

我們看下這裡做了什麼樣的操作

  1. 我們引入了一個vuex模組,並作為vue外掛啟用
  2. 我們的倉庫store不再是一個普通的jsonObject而是一個vuex.store的例項
  3. 採用了mutation,用於改變state

why better ?

  1. 如果在開發過程中保留了所有狀態的副本,我們可以像super hero一樣如時空穿梭般對程式碼進行除錯,記錄每一次狀態的變化
  2. 你可以構建一箇中介軟體,比如一個日誌列印器,在每次使用者提交一個action時記錄操作,當出現問題時你將會更加容易的除錯與fix bug
  3. 強制要求你把所有action通過一個store管理,這樣團隊開發將會更加便捷與明瞭

總結

通過長篇的分析我們可以得出本文結論了。

  1. vuex是官方推出的,事件匯流排是高手在民間
  2. 在大型應用方面,vuex確實是一個比EventBus更好的解決方案
  3. vuex更加易於除錯與管理
  4. Vuex並不是最佳的解決方案,在某些小型應用上,你可能只有小部分的資料互動,甚至只有一個登入狀態儲存,那樣事件匯流排或者簡單狀態管理都是值得推薦的。
    最後說一句,vuex真香。 ps:這只是筆者的見解,有什麼問題歡迎大家踴躍提出指正。?

相關文章