iT邦幫忙

2021 iThome 鐵人賽

DAY 21
0
Modern Web

Flutter web 的奇妙冒險系列 第 21

Day 21 | 狀態管理套件 MobX - 到底什麼是狀態管理

狀態管理?

在介紹 MobX 以前我想先來說一下什麼是「狀態管理」

究竟為什麼我們需要「狀態管理」,還記得前幾篇想到 Flutter其中一個概念 UI = F(State) 嗎,其實這個概念是借鑒 React 的設計概念。我們所看的畫面都是因為狀態經過某些function後形成的,但為什麼要這樣?

如果是寫過 JQuery 的讀者應該有體驗過要自己手動更新畫面的痛苦,如果沒有特別額外寫什麼框架的話 JQuery 就是我們需要在 dom上綁定我們的互動事件,然後在這個事件裡執行更新值之後再從這個事件去選擇我們要更新的dom並把值注入給它,上述一切都是要自己手動撰寫的。

而到了之後的 reactive 式的前端(不論是網頁或者App或者哪個框架)大多數時候我們是將UI綁定狀態及事件,並在事件裡去實作更新狀態,當狀態更新後會自動更新UI。

我個人是認為這種方案對開發者比較友善,畢竟按照目前前端的趨勢來說互動會愈來愈複雜,如果這類事情還要自己手動做也未免太累人。但也因為是自動更新UI所以必定會有不必要的re-render以及生命週期相關的事情要處理,但這系列文章應該不會說到這邊去。

說回 Flutter ,在 Flutter 裡內建的狀態管理是使用 setState 這個東西,它是一個叫做 Stateabstract class 下的 method。它調用後會觸發 build() 進行重新渲染。
使用上很簡單我就在 extends Stateclass 裡宣告幾個mutable的值,在預設範例裡是 _MyHomePageState ,然後將這些值放到build()的widget裡,如果要讓值變更時就是使用 setState 來進行值的更新。

那為什麼我們需要其他方法來幫我們管理狀態,我自己認為至少有這幾個理由

  1. UI與邏輯耦合
  2. 跨組件共用state
  3. 要傳遞到深層widget時會很麻煩

先說在前面這幾個問題就算不套用任何框架、套件就也能靠著原生方法來解決,但是就是沒那麼簡單使用或者就程式碼長了點醜了點而已。

UI與邏輯耦合

你可能會發現我們用了 setState 後我們得在UI層(先不討論到底要如何劃分範疇下總之就是指我們撰寫UI的那個class裡)宣告一堆function,更何況在實際開發場景中還要再加上後端資料的相關問題。
但這真的是必須在UI層裡實作的事情嗎?難道沒有一些辦法可以將setState 相關的事情拿出去別的地方做,然後我的UI層只要等著接收資料就好了嗎?

跨組件共用state

今天如果都只是單向的傳遞從最上面往下傳,其實都還算好理解,但如果是又兩個子widget其中一邊要根據另外一邊事件進行更新,那又該怎麼辦?

傳遞到深層widget

依目前範例來說的確是沒辦法顯示這個問題點,畢竟說真的也不怎麼複雜所以也沒有額外切分widget,但是因為flutter 的 aggressive composability 的關係會讓我們每個widget的巢狀結構會變得十分地深更加導致我們切更層的widget。這也會導致我們要將狀態/事件向下傳的層數變得更多,而這之間每一層的widget我們都要在他的constructor 加上這個 state ,並且每個也都要傳入,widget層數一多起來實在是令人煩躁。

那我們要如何解決的這幾個問題?

以「UI與邏輯耦合」來說,我們就是將各個區塊職責分離乾淨,誰該做什麼事情誰只能做什麼事情從一開始就劃分清楚。

依我自己在 react 開發經驗來說,在沒有redux(一個狀態管理套件)的情況下我會盡量讓狀態統一在一個 component 裡宣告,也包含了fetch api data等等跟資料相關的事情。之後再寫各種 function 將 setState (react hook的setState跟flutter不一樣)包裝起來往下傳到子component 的props。但為什麼不是直接傳setState 而是要幫裝一層,其中一個原因就是

我不想讓子 component 裡可以任意的改變狀態,而是要按照我上面規定好的邏輯進行變更,也就是我不想把state更新邏輯寫在子 componet 我希望愈下層的component只要做好純粹顯示這個功能就好。

某種程度上他已經部分解決了「UI與邏輯耦合」,但實際上只是藉由將state上升統一管理,但這段更新邏輯還是存在在component裡。

所以更進一步的話就是使用了 redux 來將邏輯完全抽離出去,而且我只要在子component這裡select及dispatch就好,根本不用在上層component額外宣告那些handler再傳下來。

聽起來好像順便解決了「傳遞到深層widget」及「跨組件共用state」但這是react-redux的useSelector及useDispatch順便解決的問題,否則你還是要將外部的那個東西(其實就是store)一層一層傳下去再取出來。

而在 Flutter 中有很多套件可以解決「傳遞到深層widget」以及「跨組件共用state」,像是provider、riverpod、getx等等很多很多套件可以做到這些事情。我們可以套過這些套件藉由一些方法幫我們統整狀態甚至其他東西放到最上層,而當我們需要時再取出來。(沒錯這聽起來很像依賴注入)

MobX

所以接下來我們將會使用 MobX 這個套件來進行狀態管理,我自己的觀點是 MobX 是一個「純粹的狀態管理套件」,其實他沒辦法幫我們解決「傳遞到深層widget」以及「跨組件共用state」,那為什麼要用它而不是用其他的套件呢?最主要的原因就是它夠簡單,對於初學者的心智負擔較小。

MobX 最主要的功能是幫我們簡單的連接狀態與UI,而其中是靠著 observer pattern 來達成這件事情。observer pattern 簡單來說是觀察者(observer)更新後會自動通知觀察者(observables)

MobX整個流程圖會像是這樣:

https://ithelp.ithome.com.tw/upload/images/20211004/20112906JbcX7rdTIS.png

有點像是簡單版的redux,我們所有的狀態變更都是藉由 Action 來更新之後會更新狀態也就是(observable) 然後如果有 computed 會再計算一層之後再觸發更新(也就是叫observer更新)

在MobX中我們可以將這一切包進一個 class 裡然後將邏輯都寫在那邊,那我的UI就只剩從這個class 取資料以及action了。

通常這個 class 就會被稱為 store 之類的稱呼,所以如果我要跨組件共用這個 store 我就是要藉由其他套件將這個 store 傳到其他的子widge,而不是直接將狀態傳出去。


這篇文寫起來有點無聊但感覺也是在講狀態管理套件前必須要說明的內容。

雖然 MobX 也是從 react 過來的套件,但其實我在 react 裡完全沒有使用過 MobX 原因是 redux 在社群的聲量遠大於MobX,而且在目前react hook的環境裡感覺 redux 會比較搭,但也因為 Flutter 的並沒有 hook 所以我在使用 redux 會覺得卡卡的。
(我知道有hooks_widget,我使用過但感覺還是不太像 react hook那樣。)

但我覺得如果我不是兩邊都有在開發應該也不會有這種卡卡的感覺,最主要是現在 redux-toolkit讓我在react中使用redux的開發體驗實在是太舒服了。

參考資料

https://mobx.netlify.app/


上一篇
Day 20 | 萬年範例-TodoList(3)
下一篇
Day 22 | 狀態管理套件 MobX - 基本使用
系列文
Flutter web 的奇妙冒險30

尚未有邦友留言

立即登入留言