Elm 中使用子模組的一種方法
為什麼需要引入子模組?
隨著程式碼量的增加,相關邏輯日漸複雜,需要維護的狀態和傳遞的訊息也迅速的增加起來。
Elm 的架構文件中並沒有詳細說明如何組織比較複雜的專案,我調查中看到的文章中的方案也大多仍然需要模組間的耦合,實際使用中並不能得到滿意的效果。
期望達到的效果
首先需要做到的是程式碼層面的分離,模組內部實現細節的修改對外部來說盡量不可見,減少程式碼的耦合程度,便於開發。
下一個階段的目標是模組的可重用性,除了簡單的函式層面的重用,在更高層次上也有很多相似性,例如如果由於應用場景的考慮,需要釋出多個微信小程式的話,其中有不少邏輯是可以共用的,例如微信端使用者登入、資訊獲取,服務後臺的 Session 管理,等等。
模組之間的互動應儘量簡單,可以用可維護的方式進行組織。
如何拆分子模組
個人的習慣是先從資料開始設計,在 Model 的部分先做分割,之後進行 Msg 的設計,宗旨是把聚合度高的部分放在一起,封裝成獨立的模組。
子模組間如何互動
多個模組需要彼此協調才能完成完整的應用邏輯,根據具體情況有以下的情境
資料依賴
某個模組需要外部提供所需的資料,有幾種處理的方法,可以根據具體需要進行選擇
- 作為輸入事件的引數傳遞進來,只在相關事件的處理中使用
- 封裝成內部的資料,加入 Model,在需要時訪問
- 作為 update 方法的引數,每次更新時都可以訪問到
事件觸發
對於子模組來說,其實不用瞭解事件的具體來源,可以是模組自身,可以是其它模組,或是應用層面的使用者輸入。只要把自身的生命週期管理好即可,由於 Elm 架構的函式式和不可變特性,一般來說除錯也很方便,只要觀察 Msg 的序列以及相應的 Model 的變化往往就能找到問題所在。
WxApp 子模組
由於所有的微信小程式都需要進行使用者身份的管理,在 elm-wx-app 中提供了一個基本的身份認證子模組,在 API 呼叫之上提供了更高一層的介面。
下面列出了部分的程式碼,結構相對比較簡單,感興趣的話可以 Clone 完整的版本。
(目前的版本還比較簡單,介面也沒有完全固定下來)
Model
type alias Type =
{ systemInfo : SystemInfo.Type
, userCode : String
, userInfo : UserInfo.Type
, userSecret : UserSecret.Type
, tabs : List UiTab.Type
, currentTabKey : UiTab.Key
, pages : List UiPage.Type
}
Msg
type Msg
= DoInit
| DoGetSystemInfo
| DoCheckSession
| DoLogin
| DoLoadWxModel
| DoGetUserInfo
| GetSystemInfoMsg (Result Error GetSystemInfo.Msg)
| CheckSessionMsg (Result Error CheckSession.Msg)
| LoginMsg (Result Error Login.Msg)
| LoadWxModelMsg (Result Error WxModel.Type)
...
update
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
DoInit ->
( model
, cmd DoGetSystemInfo
)
DoGetSystemInfo ->
( model
, GetSystemInfo.cmd GetSystemInfoMsg
)
DoCheckSession ->
( model
, CheckSession.cmd CheckSessionMsg
)
DoLogin ->
( model
, Login.cmd LoginMsg
)
...
WxApp Wrapper 例項分析
wrapper
wrapper 的細節請看 elm-component-updater 的實現程式碼,基本上是從主模型中訪問子模型(get),呼叫子模組的 update,之後再把返回的子模型更新到主模型中(set)
wrapper : Wrapper Msg Wx.Msg
wrapper =
wrap WxMod
{ get = Just << .wx
, set = \modModel model -> { model | wx = modModel }
, update = Wx.update
, react = reaction
}
cmd msg =
toCmd msg
|> Cmd.map wrapper
reaction
reaction 的目的是對於特定的子模組事件產生相應的外部事件,來達到對其他模組的控制。
reaction modMsg modModel model =
model ! []
|> case modMsg of
Wx.PopPageMsg pageKey (Ok _) ->
case List.length modModel.pages of
0 ->
addCmd <| cmd <| Wx.SwitchTab "dialogue"
_ ->
noOperation
_ ->
noOperation
主應用中的相關程式碼
model
首先是在 Model 中包含子模組的部分
type alias Type =
{ rev : Int
, wx : Wx.Model
...
事件定義
import Updater
import WxApp.Mod as Wx
type alias Delegate = (Updater Model Msg)
type Msg
= WxMod Delegate
| WxMsg Wx.Msg
...
WxMod 代表由 Wrapper 處理的事件,WxMsg 則是普通事件,需要在 update 中轉換為 Wrapper 事件。
這裡做區分的原因是在 Elm 中無法迴圈 import,在被 WxApp Wrapper 引用的程式碼中如果也需要通知 WxApp 的模組,則只能產生一個 WxMsg 型別的事件。
update
import Wrapper.Wx as Wx
updateMod : Msg -> (Model, Cmd Msg) -> (Model, Cmd Msg)
updateMod msg (model, cmd) =
case msg of
WxMod delegate ->
delegate model
WxMsg msg ->
(model, Wx.cmd msg)
...
可以看到這裡對於 WxMsg 型別的事件使用 wrapper 做了一次轉換,略顯繁瑣,不確定是否有更好的方式。
總結
實際開發中應用以上方式寫了不少程式碼,在模組的分隔上感覺還是一種不錯的方法。
在需要調整模組結構的情況下,Elm 作為靜態型別語言提供了很大的幫助,編譯器可以發現不匹配的介面,重構起來有一氣呵成的感覺。
附錄
連結
相關文章
- 在Java中呼叫Groovy方法的又一種方法:使用介面Java
- Oracle 中不使用NOT IN 和 NOT EXISTS的另一種方法Oracle
- 一種子圖佈局方法的實現
- 使用 JavaScript 驗證電子郵件的 4 種方法JavaScript
- python中list的各種方法使用Python
- Elm架構架構
- Python模組過載的五種方法Python
- BeautifulSoup模組的使用方法
- SAP SD模組中POD功能使用方法
- Struts2中使用Session的兩種方法Session
- glom模組的使用(一)
- Python中模組的使用Python
- 老問題:Android子執行緒中更新UI的3種方法Android執行緒UI
- MachineUnlearn 的一種方法Mac
- 使用vue實現行列轉換的一種方法。Vue
- Billboards 技術在Unity 中的幾種使用方法Unity
- 檢索oracle中口令:第一種方法Oracle
- Python 中argparse模組的使用Python
- CANoe中Logging模組使用方法及妙招⭐
- Klaviyo:提高電子商務收入的11種方法
- 一種巧妙POST未選中的Checkbox的方法薦
- Excel使用中的種種限制教程Excel
- Ta想做一粒智慧的種子
- python中模組和方法的查詢Python
- vue3.0 的 Composition API 的一種使用方法VueAPI
- Dynamics 365 隱藏子網格加號的一種不受支援的開發方法
- python中sys,os,time模組的使用(包括時間格式的各種轉換)Python
- Go 中的三種排序方法Go排序
- git 子模組使用方法Git
- OpenCV中GPU模組使用OpenCVGPU
- CSS的三種使用方法CSS
- JavaScript中十種一步拷貝陣列的方法JavaScript陣列
- MySQL中處理各種重複的一些方法MySql
- Python中匯入模組中的物件常見的三種方式!Python物件
- Elm 語言初體驗
- Python中yaml模組的使用教程PythonYAML
- Oracle中刪除使用者下所有物件的多種方法Oracle物件
- js利用閉包封裝自定義模組的幾種方法JS封裝