Vuex 可能追加的功能:命名空間

Vuex 是個規劃 Vue.js 資料流的「理念工具」,一般來說規劃像是記事本或是購物車這樣小型的應用程式都游刃有餘,一但遇上大型應用程式像是社群網站就會有些問題。

好佳在的是接下來的 Vuex 版本可能會新增命名空間的功能了。

都是同個 Store

在 Vuex 中只有一個 Store。意思是當你有兩個同樣名稱的 Action 或是 Getter, Mutation 的時候,就會遇到命名重複的問題。

為什麼要命名空間?

假設程式中有兩個功能:相簿、文章。這個時候你想要替相簿新增一個名為 Create 的函式,但是因為 Store 是共享的,所以你只能新增一個 Create。為了解決這個問題你可能需要改名成 AlbumCreate(), PostCreate(),然後就會越來越長⋯⋯。為了要解決這個問題所以我們需要命名空間。

以往的做法

你要是很沒有耐心的話,可以直接往下跳到「未來的做法」章節 ;)。

在以往 Vuex 的版本並沒有提供命名空間的做法,意思是你需要自己用前綴作為命名空間,聽起來很蠢,實做起來也很蠢。這個方法的來源是:Vuex 2.0 module's actions and getters namespacing

先定義命名空間

用這種舊的做法,你需要先定義你會用到哪些名稱,像下面這樣的範例會有一個命名空間:main,一但你有了新的名稱你就必須回來這裡新增,十分地聒噪。

// types.js
import namespace from 'utils/namespace'

module.exports =  
{
    main: namespace('main',
    {
        getters: [],
        actions: [
            'toggleSidebar'
        ],
        mutations: [
            'toggleSidebar'
        ]
    })    
}

以命名空間命名函式

這還沒完,接著你要像下面用 [types.種類.名稱] 來命名你的 Action, Getter, Mutation 才能使用你剛才定義的命名空間:

// Main.js

import { main as types } from './types'

const mutations = {  
    [types.mutations.toggleSidebar] (state, sidebarName) {
        state.sidebar = sidebarName
    },
}

const actions = {  
    [types.actions.toggleSidebar] ({commit, state}, sidebarName) {
        if(state.sidebar === sidebarName)
            commit(types.mutations.toggleSidebar, null)
        else
            commit(types.mutations.toggleSidebar, sidebarName)
    },
}

const getters = {}

export default {  
    state,
    mutations,
    actions,
    getters
}

接著這樣在你的元件中使用這個命名空間中的函式。

import { mapState, mapActions, mapGetters } from 'vuex'  
import { main as types }                    from 'store/types'

export default {  
    name: 'Header',
    computed: {
        ...mapGetters(types.getters),
        ...mapState(['main'])
    },
    methods: {
        ...mapActions(types.actions)
    }
}

還真是聒噪,對嗎?一但你的應用程式越來越大,這些東西也就跟著變得雜亂無章,幸好的是接下來可能會有所好轉了。


未來的做法

這個做法的構想被討論在 #359, #380, #381 然後已經有合併請求實作了這個功能,所以我推測未來會被納入 Vuex 中才撰寫這篇文章的。這個方法比起上一種簡單、且自動太多了。

示範

這個新的方法只需要在 Module 中增加一個 namespace 就可以解決了,依照新的文件範例,像是下面這樣:

export default {  
  namespace: 'account/',

  // 模塊內容
  state: { ... }, // 模塊狀態不會因為前綴而有所改變
  getters: {
    isAdmin () { ... } // -> getters['account/isAdmin']
  },
  actions: {
    login () { ... } // -> dispatch('account/login')
  },
  mutations: {
    login () { ... } // -> commit('account/login')
  },

  // 嵌套模塊
  modules: {
    // 從父模塊繼承命名空間
    myPage: {
      state: { ... },
      getters: {
        profile () { ... } // -> getters['account/profile']
      }
    },

    // 嵌套命名空間
    posts: {
      namespace: 'posts/',

      state: { ... },
      getters: {
        popular () { ... } // -> getters['account/posts/popular']
      }
    }
  }
}

rootGetters

在命名空間內的 Getters 和 Actions 會取得到該命名空間內的 Getters、Dispatch 和 Commit。簡單來說只要是在同一個模塊內你就可以直接使用它們而毋需像以往一樣增加前綴。如果你希望存取全域、最上層的 Getters 你可以使用 rootGetters

rootGetters 會被傳入在 Action 跟 Getter 的第四個參數。

export default {  
  namespace: 'prefix/',

  getters: {
    // `getters` 的範圍僅限於這個模塊
    // 但現在有提供第四個參數 `rootGetters`
    someGetter (state, getters, rootState, rootGetters) {
      getters.someOtherGetter // -> 'prefix/someOtherGetter'
      rootGetters.someOtherGetter // -> 'someOtherGetter'
    },
    someOtherGetter: state => { ... }
  },

  actions: {
    // dispatch 和 commit 的範圍也都僅限於這個模塊
    // 但你可以用 `root` 選項來從「根」範圍進行 dispatch/commit
    someAction ({ dispatch, commit, getters, rootGetters }) {
      getters.someGetter // -> 'prefix/someGetter'
      rootGetters.someGetter // -> 'someGetter'

      dispatch('someOtherAction') // -> 'prefix/someOtherAction'
      dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'

      commit('someMutation') // -> 'prefix/someMutation'
      commit('someMutation', null, { root: true }) // -> 'someMutation'
    },
    someOtherAction (ctx, payload) { ... }
  }
}