在我們要聊元件溝通之前,我們先來聊一下關於 Vuex 這個套件。他是官方推出的狀態管理的工具,官方中文翻譯叫做倉庫(?),也是啦,如果把 store 直接翻譯的話,好像叫倉庫也是挺合理的。

但我覺得好像狀態管理會比較符合事實。


Vuex

如果你是使用 Vue CLI 的話,那就是用 yarnnpm 裝一下就好,

yarn add vuex

# OR

npm i vuex

然後在你的 App 裡面,你會有幾件事情要做:

  1. Vue.use(Vuex)
  2. 定義你的 Store。
  3. 把你的 Vue App 加入 Store 裡面。
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

let store = new Vuex.Store({
  modules: {
    // 這邊就放你要的 Modules
  }
})

export default store

我們先回過頭來講講 Vuex 的基本應用。

Vuex

Vuex 在這邊所擔任的角色,是屬於共享狀態管理機制( Shared State Management ),主要的目的是讓你在各種不同的元件中溝通。溝通是個簡單的說法,主要的目的還是用於共享某些資訊,用以達到資訊同步的目的。

當然,這個並不是訊息溝通的必要手段。官方也說,如果不是大型結構的話,你可以自己製作一個狀態儲存機制,並不一定要使用 Vuex,畢竟這過於冗餘。

Simple State Management from Scratch

那麼這個狀態管理機制裡面有什麼東西呢?

  • State 狀態儲存的物件,Vuex 使用 單一狀態樹 的方式來存放。
  • Getters 取得狀態資料的方法。
  • Mutations 更新狀態資料的方法。
  • Actions 類似 Mutations,但是 Actions 是呼叫 Mutations,且可支援非同步呼叫。
  • Modules 用於分割 Vuex 的區塊。

有了這寫核心結構概念後,我們來看看新增一個 Vuex 會有哪些屬性可以使用:

const store = new Vuex.Store({
  state: {
    age: 18
  },
  mutations: {
    incrementAge: function (state) {
      state.age++
    }
  },
  actions: {
    ohMyAge: function (commit) {
      commit('incrementAge')
    }
  },
  getters: {
    getAge: function (state) {
      return state.age
    }
  }
});

這是一個簡單的 Store 實例,如果在元件當中,你會有幾個方法,可以從 Vuex 裡面提出來使用。

import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'

相對應的關係是這樣:

  • mapState 可以取得 state 裡面的資料。
  • mapMutations 可以取得 mutations 裡面的方法。
  • mapActions 可以取得 actions 裡面的方法。
  • mapGetters 可以取得 getters 裡面的方法。

所以說,你們可能會看到這種寫法,

import { mapGetters, mapState } from 'vuex'

export default {
  name: 'MyComponent',
  computed: {
    ...mapGetters([
      'getAge'
    ]),
    ...mapState([
      'age'
    ])
  }
}

這麼你會在 template 當中可以直接使用 {{ getAge }},或是你在你的元件裡面,可以使用 this.getAge 來取得資料。

而如果是 mapActions 或是 mapMutations 則是放在 methods 裡面,

import { mapMutations, mapActions } from 'vuex'

export default {
  name: 'MyComponent',
  methods: {
    ...mapActions([
      'ohMyAge'
    ]),
    ...mapMutations([
      'incrementAge'
    ])
  }
}

這樣你應該知道這些東西相對應在元件或是 App 裡面可以怎麼使用了。接著,Vuex 還有一些其他的設定:

  1. strict: true 啟用嚴謹模式,當你開啟後,你只能透過 Mutations 來改 state 的數值,如果直接更改你的 state 數值,會被警告。
  2. 支援 插件Plugin ),你可以自己做想要的工具放進去。
  3. 在開發模式下,支援 熱重載Hot reloading ),這個寫法可以參考官方文件,在開發模式下適用。

最後,剛剛所提到的 Modules 這個設計,倘若你的結構相當大,那麼為了區分不同的區塊,你可以使用 modules 來區隔,

const hello = {
  state: {
    // 狀態資料
  },
  mutations: {
    // 操作方法
  }
}

const kitty = {
  state: {
    // 狀態資料
  },
  mutations: {
    // 操作方法
  }
}

const store = new Vuex.Store({
  modules: {
    hello: hello,
    kitty: kitty
  }
});

這樣你的 Vuex 在元件當中,就利用 store.state.hello 來取得 hello 的 Module 當中的資要。

new Vue({
  computed: {
    getHello: function () {
      return store.state.hello
    },
    getKitty: function () {
      return store.state.kitty
    }
  }
}).$mount('#app')

這裡還有另外一個設定,叫做 namespaced。一般來說,無論是你是否使用 Modules,裡面的元件在沒有設定 namespaced: true 的情況下,mutations, actions, commit, dispatchgetters,大家都是綁訂在 全域 的料結構內,所以,如果依照剛剛的例子來看:

你會看到在 getters 裡面有兩個東西,一個叫做 getAAge 另一個叫做 getBAge,如果我們把 getAAge 這個 Module 加上 namespaced: true 之後,他就會變成這樣:

所以說,這樣我們再使用 Getters 的時候,寫法就會略有差異:

import { mapGetters } from 'vuex'

export default {
  name: 'MyComponent',
  computed: {
    ...mapGetters({
      getAAge: 'a/getAAge',
      getBAge: 'getBAge'
    })
  }
}

這些情況,在 mapActions, mapMutationsmapGetters 上面都適用。而,如果你的 Module 底下還有 Module,且也開啟 namespaced: true 的話,那就可能會有這種結構出現:

import { mapGetters } from 'vuex'

export default {
  name: 'MyComponent',
  computed: {
    ...mapGetters({
      getAAge: 'a/other/module/getAge'
    })
  }
}

最後,關於 Module 這個部分,也可以使用 registerModule 這個方法來註冊你的 Module,例如說:

store.registerModule('myModule', {
  // 你的 Module
})

你也可以註冊巢狀的 Module,例如:

store.registerModule(
  [ 'myModule', 'nested' ],
  {
    // 你的 Module
  }
)

另外,Vuex 另外也提供了一個方法,叫做 createNamespacedHelpers 用來取得有命名空間的資料,主要的差異用範例來看比較清楚。

這是原本的寫法,

import { mapGetters } from 'vuex'

export default {
  name: 'MyComponent',
  computed: {
    ...mapGetters({
      getAAge: 'a/other/module/getAge'
    })
  }
}

這是使用了 createNamespacedHelpers 的寫法,

import { createNamespacedHelpers } from 'vuex'
import { mapGetters } from createNamespacedHelpers('a/other/module')

export default {
  name: 'MyComponent',
  computed: {
    ...mapGetters({
      getAAge: 'getAge'
    })
  }
}

這樣看得出差異了嗎?當然,這個大前提是,如果你要取得的資訊是在同一組 Module 裡面,如果你是要跨出其他的 Module 的話,那麼就不能用這個方式。


關於非同步處理

如果你開啟了嚴謹模式,那麼你要修改資料就只能透過 Mutations 來做,但是,這個方法 不可以 使用非同步處理,所以,你必須要透過 Actions 來處理關於非同步的事情。

那麼,在 Actions 裡面,我們會有幾個東西可以使用:

const store = new Vuex.Store({
  state: {
    hello: ''
  },
  mutations: {
    setHello: function (state, data) {
      state.hello = data
    }
  },
  actions: {
    setHello: function ({ commit, dispatch, rootState, state }) {
      // 這邊可以做非同步處理,例如 AJAX
      axios({
        method: 'get',
        url: '/get/something',
        responseType: 'json'
      }).then(function (res) {
        commit('setHello', res.data)
      });
    }
  },
  getters: {
    getHello: function (state) {
      return state.hello
    }
  }
});

你會看到你可以使用:

  • commit 這個函式是呼叫 mutation 裡面的方法。
  • dispatch 這個函式是呼叫 action 裡面的方法。
  • rootState 這個物件是整個 Store 的 state 資料。
  • state 這個物件是本地端( Local state )的資料。
  • rootGetters 這個物件是整個 Store 的 getters 資料。

所以,當我們做完 axios 的非同步傳輸後,可以使用 commit 或是 dispatch 來接續後面要做的事情。而 mutation 裡面,僅允許同步操作,這樣的非同步操作在 mutation 裡面是不能使用的。

其他具體的操作可以參考官方說明:

Vuex Store

特別提到一點,剛剛的 commitdispatch 由於 namespaced 的設定的關係,所以,他們的第三個參數,可以指定 { root: true },表示從 Vuex 根元件做呼叫一個方法,他可以根據你的 Module 的設定來 到你指定的目標。

store.commit('a/other/module', {}, { root: true });
store.dispatch('a/other/module', {}, { root: true })

這個方法的應用情境,主要是在 不同的 Module 當中,需要去呼叫 其他的 Module 的時候,你必須要加上這個參數,這樣才能觸發到你想要的目標。不然依照 namespaced: true 的設定,在你的 Module 裡面的 commit 都是觸發你的本地端( Local state )的方法(包括 commit 或是 dispatch)。

如果你的 Module 是 namespaced: true 的話,第三個參數沒有設定,就會被回報錯誤!

所以,當你在 Module 裡面,且 namespaced: true 的時候,想要呼叫其他人,就必須指定第三個參數為 { root: true } 來告訴他,請從 的位置去呼叫。上述的例子,你會看到我所展開的 Store 裡面,關於 _actions 的部分,其中包含了 a/setAgesetBAge 這兩個方法,這裡其實就是 的位置。

這樣,你應該可以理解第三個參數 { root: true } 實際上做了什麼事情了。


小結

這裡只是粗略的介紹一下 Vuex 的功能,實際上使用還是得看你的專案是不是真的需要。就如同官方說的,小專案用這樣的工具,確實是有點冗餘。

如果你是為了潮,那可以(欸不對)!

vuex 的五十道陰影

ITHome 鐵人賽同步刊登 Vuex 基本入門 Day 8