我們會把 Vuex 放的比較前面,是因為既然使用了狀態管理機制,那麼,用他來跟各種元件溝通,是比較方便的一件事情。然後,我拖了那麼久才講到元件溝通好像有點奇怪,也是啦,其實我本來就不是很喜歡溝通(欸不對)。

是說,溝通只是 Vuex 附加的好處而已其實。


元件交換資料

我們之前有提過元件之間的資料交換,也提過 Vuex 的基本應用:

之前說過 Vuex 是幫我們做狀態管理的,所以說我們拿來做元件交換資料也是很適切的事情。首先你知道 Vuex 有所謂的 state 提供我們放置資料,而 mutations, actions 拿來更新資料,然後 getters 用於取出。所以,我們每個元件當中,都可以利用這個方式來交換資料。

import { mapGetters } from 'vuex'

export default {
  name: 'AComponent',
  computed: {
    ...mapGetters({
      getUsers: 'users/get'
    })
  }
}
import { mapGetters } from 'vuex'

export default {
  name: 'BComponent',
  computed: {
    ...mapGetters({
      getUsers: 'users/get'
    })
  }
}

當然你把 mapGetters 換成 mapState 也是相通的。不過再次提醒一下,在嚴謹模式下,你不能針對 Getters 或是 State 取出來的資料作任何的更動。所以,你就只能通過 Mutations 或是 Actions 去更動他。

那麼我們在使用 Mutations 更動的時候,各個元件的 Getters 是不是真的更新了呢?

我們有兩種方式去得知你的 Getters 有更新,其一是使用 updated 這個生命週期的勾子,其二是使用 watch 這個方法。從上述的例子,我們使用 watch 來監聽一個 user 的物件,使用 updated 來監聽元件更新。差異在哪?我們來看一個例子:

上面這個範例,我們維持 watch 的方式來監聽 getUser 這個資料,然後你會發現,怎麼 watch 沒有被觸發了?接著,我們底下使用 updated 來看看 getUser 發生了什麼事情。

我們從 updatedgetUser 拉出來看,會發現資料確實有更新。那麼,為何 watch 會沒有被觸發呢?關於 watch 的部分,由於某些緣故,你必須使用 deep 個的第三個參數才會有正常的反應,我們後續會有一個篇幅來談談 watch 這件事情。

有在關注我的部落格的人,應該有看過我嘴過 Vue 的 watch 才是。


確保資料同步

我們先撇除 watch 的毛病,我們可以利用 Getters, State 來確保兩個元件都可拿到同一份資料。當然,這一份資料必須要能確保已經沒有任何非同步操作。還記得 Actions 可以使用非同步操作嗎?

const store = new Vuex.Store({
  state: {
    user: {}
  },
  mutations: {
    setUser: function (state, user) {
      state.user = user
    }
  },
  actions: {
    fetchUser: function (commit) {
      return axios({
        url: '/get_user'
      }).then(res => {
        commit('setUser', res.user)
      }).catch(() => {
        commit('setUser', {})
      })
    }
  },
  getters: {
    getUser: function (state) {
      return state.user
    }
  }
});

如果你在元件 A 當中呼叫了 fetchUser,而同時 B 元件使用 Getters 去讀取 user 的時候,你就有機會會因為非同步傳輸的狀況,導致兩者的資料不相同。所以說,倘若是你使用 watch 或是 updated 來監聽,就會有一點點 延遲 的狀況。

所以說,倘若你有很多個元件,倘若需要確保資料同步或是資料呈現一致,你需要留意幾件事情:

  • 注意 updatedwatch 延遲。
  • 可以搭配 EventBus 來做全域通知。
  • 利用特定 flag 來阻斷同一個資料的 連續 操作。

延遲的事情就如同剛剛提及的非同步傳輸造成,而 watch 衍生的事情我們後續會再聊聊。除了這些,你也是可以搭配 EventBus 來做通知,換句話說,當你的 Mutations 或 Actions 在做 commit 或是 dispatch 的時候,可以同時觸發一個 Event 讓你的元件及時更新。

至於 阻斷連續操作 這件事,你就把他想成,當我想要收藏某一個貼文,或是我們去臉書按讚的時候,是否允許連續操作。像是這種狀況,我會有兩種作法:

  1. 第一次按下「讚」,立一個 flag 阻斷他,直到 XHR 返回。
  2. 第 N 次按下「讚」,只會送出最後一次的請求,然後等 XHR 返回。

無論你採用什麼方式,最後都還是會有 Getters 或是 States 要等 XHR 回來的狀況,這些時候你就必須小心處理。


又是薛丁格

就如同上述的例子,除了你可能看不到變更,但是好像又有變更,將他使用 console.log 印出來他又真的有變更。

薛丁格甲賽啦!

多數狀況是所謂的「時機點」的問題,就如同上個段落所提及的,使用 EventBus 來做一個「通知」,這樣的動作就是強迫觸發一個事件,讓這個事件再次去提取你所想要的資訊。這樣你就能肯定在事件觸發的同時,資料已經有所變動。

如果事件觸發後資料沒改變,那一定是你自己的問題!
不要老是叫薛丁格去甲賽!

我們其實在元件趨於複雜時,對於 Vuex 的設定也會過於冗長,除了妥善利用 namespaced 來適時的區格外,資料結構相對的也需要妥善規劃。以下提出一些實作上的建議給大家參考:

  • 請不要在 state 裡面有資料關連。
  • 當你的資料是一個物件( Object )時,請留意更新方式:
    • 你是要更新 一個 還是 全部
    • 特別會更新 某個 屬性資料時,建議另外寫。
    • 若要更新 全部 請確保你的資料不會遺漏。
  • 當你的資料是一個陣列時,請留意更新方式:
    • 請注意 index 的變化。
    • 換掉 一個 或是 全部 ,你的 index 可能都不會變。
    • 注意型別,在 JavaScript 裡面他也叫做 object
  • 請留意 state 用於 computed 時,Render Function 的告警:
    • 無相關屬性,在渲染時會有警告或錯誤,請確保資料屬性存在。
    • 同上述,更新 全部 資料,請確保資料屬性一致性。
    • 預設值給好給滿,一生平安,出門撿到錢

很多時候你必須要不斷的試錯才會發現一些問題,但這有時候有很難歸咎於他是個問題。只能說這些事情變成了各種工具、套件或是框架本身的「特性」而不能算是個錯誤或是什麼的。

至少這邊我有先踩到了一些看起來很蠢的雷,你們就不會再犯了。


小結

其實我沒有講很多「溝通」的事情。對於 Vuex 來說溝通真的只是附加價值,但是他也是能有效的在各種元件中暢行無阻。也不會有父元件、子元件的問題,也沒有事件傳遞的問題。

啊,對了,父元件、子元件與事件傳遞的問題,我們下一篇會提到。

ITHome 鐵人賽同步刊登 Component 的溝通方式 Vuex Day 13