iT邦幫忙

2021 iThome 鐵人賽

DAY 12
1
Modern Web

Vue.js 進階心法系列 第 12

Vuex 的使用偏好

這是我個人的使用偏好,而且是以抽象資料型別的使用方式來理解 vuex 的使用方式。也許,我是說也許啦!和官網的不太一樣。

昨天介紹了物件設計的核心原則,就是不要直接修改資料,務必要透過抽象行為來修改,以保持彈性與添加限制的同時,可以維護概念整體性。

這個原則,在後端也適用於 MVC 的 Module

module 的 setter: mutation

Mutations | Vuex 的開章明義就說,想要改變 state ,就要 over my dead body (透過 mutation)。

The only way to actually change state in a Vuex store is by committing a mutation.

所以,其實 mutation 本身就隱含著 setter 的任務。必須單純的修改值,並且適時的可以加入定義域限制。

「mutation 是唯一可以改變 state 的角色」,意思是說改變的同時要可以觸發修改畫面。所以,修改的原則,也是要符合 immutable,或者用 Vue 提供的 API Vue.set(obj, 'newProp', 123)

  • Use Vue.set(obj, 'newProp', 123), or

  • Replace that Object with a fresh one. For example, using the object spread syntax (opens new window)we can write it like this:

state.obj = { ...state.obj, newProp: 123 }

不過我覺得「靠自己,好自在」。所以只要修改值,就要注意 immutable,以及觸發自動修改畫面的機制 (符合 MVVM 的精神)

實務上的狀況

通常 mutation 是在接受 Web API 之後將 Web API 的資料 (通常是 GET 的 API) 儲存到 state。

而這個步驟就足以決定你的 vuex 管理是否簡單或複雜。

請遵守這些原則

命名不要用動詞
命名要與 getters 同名
命名要注意單數/複數

因為使用的時候是

  • this.$store.commit('user', data)
  • this.$store.commit('users', data)

光這一行,你看得出什麼樣的資訊?看不出什麼樣的資訊呢?

  • [x] 要 set 進 state
  • [x] 要儲存到 user 的 module,是單數資料 (一筆)

看得出這樣的資訊就夠了。

至於 set 進 user 需不需要定義域的阻擋邏輯,通常 API 來的資料,都已經通過畫面的欄位驗證以及後端在接收到 POST 的時的驗證,資料經過了這些驗證而進入資料庫,所以在取得資料時,其實應該相信這些機制的作用,不用在這個環節再阻擋或驗證一次。

但如果,你的 commit 是直接透過畫面把值設定進來,也許需要在 Mutation 加入驗證,因為要確保 state 是正確的,才不會影響 getters 在輸出資料時,還要寫更多的邏輯處理。

Actions 呢?

Actions | Vuex

官網的 Actions 說,它可以寫非同步行為,而且要用 action 呼叫 mutation

mutation 不要寫非同步行為
不要用 action 直接改 state

官網說建議要透過 action 呼叫 mutation 的理由,是萬一你要非同步的使用 mutation 的話,就可以直接添加在 action。

我自己的解讀是「如果沒有要非同步,直接 commit 也是可以」
所以,我自己的用法會是「同步行為與非同步行為,分成 action 和 muation」

實務上的狀況

如果看見這樣寫

this.$store.dispatch('fetchUsers')

就是它要發一個 API 去請求 User 的列表,並且會在裡面呼叫 commit('users', res.data) 將 response 的 body 儲存在 state 裡。

如果看見這樣寫

this.$store.commit('users', users)

就只是將資料儲存到 state 裡。

如果看見這樣寫

this.$store.dispatch('users', users)

我會不知道,這是非同步的還是同步的行為,中間經造了什麼將 users 儲存到 state 裡。

請遵守這些原則

命名要加上動詞

以 User Module 為例 action 的命名會依 API 的用途為主。CRUD 的話已經有一定的命名原則。

行為 命名 實際使用
新增 createUser $store.dispatch('createUser')
讀取 fetchUser $store.dispatch('fetchUser', id)
讀取 fetchUsers $store.dispatch('fetchUsers')
修改 updateUser $store.dispatch('updateUser', id)
刪除 deleteUsers $store.dispatch('deleteUsers')
刪除 deleteUser $store.dispatch('deleteUser', id)

命名要具體的描述行為,不需與 API 相同

有時候網站會有一些留言的機制。而留言的目的也許有不同的 type
有時 API 的開法, type 並不是參數,而是 path 的一部份

  • POST /message/issue
  • POST /message/comments

這個 issues 和 comments 就會把 actions 設計成

  • $store.dispatch('createMessage', { type, body })
    • type: comments | issue

這樣 actions 的命名,是一種「使用非同步行為」的描述,而不只是非同步行為轉變形式的樣子

命名要注意單數/複數

名詞的部份和 commit 一樣,而且也是隱喻著要 commit 到哪裡去。

module 的 getter: getters

Getters | Vuex

根據 state 的狀態衍生出其它的資料。
可以理解是一種共用的 computed 也可以理解是一種「整理資料的資料邏輯」

如果今天,在修改使用者 (ex: id=1) user 的表單中,需要設定該 user 的主管,這個主管會是由一個下拉式選單來顯示。

那麼資料該怎麼取得呢?

  • 取得原本使用者的資料
    • $store.dispatch('fetchUser', 1)
  • 取得主管下拉式選單內容
    • $store.dispatch('fetchUsers')
    • $store.getters.leaders
    • leaderOptions (在 computed 裡或 getters 也可以)

取得主管的下拉式選單中,有一個 $store.getters.leaders

getters: {
  leaders(state, getters) {
    const departments = getters.departments;
    return state.users.filter(user => departments.some(department => department.leader === user))
  }
}

將整理資料的程式碼寫在 getters ,而不是在 mutation 的時候整理資料,有幾個好處

  • 後端的 API 可以單純一點,前端依自己的畫面需求整理資料。
  • 有些資料來源相同,可以降低發送 API 的資數

觀察它們的參數

mutation

第一個參數是 state (所屬的 module 裡的 state)
第二個參數是 payload (帶什麼新東西要來修改值)?

mutation: {
  user (state, payload) {
    state.user = payload
  }
}

getters

第一個參數是 state (所屬的 module 裡的 state)
第二個參數是跨 module 的 getters (不直接取得 state)

getters: {
  leaders(state, getters) {
    const departments = getters.departments;
    return state.users.filter(user => departments.some(department => department.leader === user))
  }
}

actions

第一個參數是 context 跨 module 取得 dispatch, commit, getters (不直接取得 state)
第二個參數這次傳進來的參數,用起來和 mutation 的 payload 一樣。

actions: {
  fetchUser ({ dispatch, commit, getters }, user_id) {
    // ...
  }
}

透過觀察參數可以發現 mutation 和 getters 的設計,修改/讀取都是以 state 為核心。而 actions 比較像是面對這些 module 的獨立 function 。有點像是後端的 controller。

所以,我在使用 vuex 的資料夾規畫上面。只會把 actions.js 獨立成一個檔案。state, mutation, getters 這三樣,會是放在一起的,形成了一個有自己 getter/setter 的抽象資料型別。


上一篇
存放資料的 state、module
下一篇
處理 API 層次感之地基篇
系列文
Vue.js 進階心法30

尚未有邦友留言

立即登入留言