この記事ではelm-update-builderというライブラリーを紹介します。
GitHubのリポジトリーにもスターがほしいやぎぃ
https://github.com/arowM/elm-update-builder
elm-update-builderを使うと、以下のようにupdate関数における処理の流れを分かりやすく記述できます。
Update.with (FD.run formDecoder)
[ Update.onErr
[ \_ -> showError
]
, Update.onOk
[ submit
, \_ -> makeFormBusy
]
]
従来のupdate関数が持つ欠点
従来のThe Elm Architectureでは、update関数は以下のような型をしています。
update : msg -> model -> (model, Cmd msg)
これはこれで分かりやすくて良いのですが欠点もあります。その欠点について説明するために、サンプルアプリケーションを取りあげます。そのうえで、elm-update-builderを使って従来のupdate関数が持つ欠点を解決していきましょう。
サンプルアプリケーションの概要
例として取り上げるのは、ヤギさんの個人情報を登録するフォームです。まずはモデルを定義しましょう。
type alias Model =
{ isBusy : Bool -- 送信中かどうか
, showError : Bool -- エラーを画面上に表示するかどうか
, name : String -- 名前入力欄の値
, color : String -- 毛色入力欄の値
}
init : ( Model, Cmd Msg )
init =
( { isBusy = False
, showError = False
, name = ""
, color = ""
}
, Cmd.none
)
モデル定義から分かるとおり、このフォームには名前と毛色の入力欄があります。それぞれの入力欄は必須項目で、空欄の場合にはエラーを表示したいと思います。
でも、フォームを開いた瞬間はどうでしょうか? 名前と毛色が空欄の状態です。ということは、そのままバリデーションを行うといきなりエラーが表示されてしまいます。何も悪いことをしていないのに、フォームを開いただけで「お前は間違ってる」みたいに言われるのは気分が良いものではありません。
そこで、showError
を用意しました。「True
のときのみエラーを画面に表示しろ」というフラグです。初期状態ではFalse
にしておけば、いきなりエラーが表示されることはありません。送信ボタンを押されてはじめてTrue
にすることで、画面にエラーを表示します。
isBusy
は送信中かどうかのフラグです。これによって短気なヤギさんが送信ボタンを2本のヒヅメで連打しても不具合が起きないように対策できます。
日本語で流れを記述する
さて、できるだけいい感じのUXを実現するために、まずは処理の流れを日本語で記述してみましょう。いきなりプログラムを書いてしまったら、作り終わった後で「もっとこうしたら良かった」と気づいて大きな手戻りになってしまいます。日本語で記述するだけなら、他の開発者にレビュー依頼して改定するのもかんたんです。
もちろん、片手間にはじめるUXデザイン - ノンUXデザイナーズUXデザイン -で紹介した通り、本当はシナリオから書いたほうが良いUXになります。でも話が長くなるので省略します。
ここでは、送信ボタンを押したときの挙動を考えます。以下のような流れにしてみましょう。
* 入力内容がおかしくないかチェックする
* おかしい場合:
* エラーを画面に表示するように変更
* 問題ない場合:
* 入力内容をサーバーに送信開始
* フォームを「送信中」の状態に変更する
従来のupdateによる記述
従来のTEAでこれを実現するには、update
関数を以下のように記述するはずです。
import Form.Decoder as FD
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
SubmitForm ->
case FD.run formDecoder model of
Ok profile ->
( { model
| isBusy = True
}
, submit profile
)
Err _ ->
( { model
| showError = True
}
, Cmd.none
)
submit : Profile -> Cmd Msg
submit profile = Debug.todo "Cmd to submit profile"
最初にインポートしているモジュールはarowM/elm-form-decoderです。これはフォームのバリデーションと同時に、送信用データなどへの変換も行える便利なライブラリーです。フォームを作るなら使って損はないライブラリーです。詳細はフォームバリデーションからフォームデコーディングの時代へをご覧ください。
今回は単純な処理だったので、日本語で記述した流れとの対応がある程度わかります。それでも、対応関係がなんだか直感的ではありません。これが従来のupdate関数が抱える欠点です。
update-builderによる解決
この問題を解決して処理の流れを分かりやすく記述できるのがelm-update-builderのメリットの1つです。具体的には以下のように記述できます。
import Form.Decoder as FD
import Update exposing (Update)
update : Msg -> Update Model Msg
update msg =
case msg of
SubmitForm ->
Update.with (FD.run formDecoder)
[ Update.onErr
[ \_ -> showError
]
, Update.onOk
[ submit
, \_ -> makeFormBusy
]
]
showError : Update Model Msg
showError = Update.modify <| \model -> { model | showError = True }
makeFormBusy : Update Model Msg
makeFormBusy = Update.modify <| \model -> { model | isBusy = True }
submit : Profile -> Update Model Msg
submit profile = Update.push <| \model -> Debug.todo "Cmd to submit profile"
処理の流れがぐっと分かりやすくなりました。あらためて日本語で記述した流れと比較してみましょう。驚異の再現度でほぼそのまま逐語訳になっていることが分かります。
* 入力内容がおかしくないかチェックする
* おかしい場合:
* エラーを画面に表示するように変更
* 問題ない場合:
* 入力内容をサーバーに送信開始
* フォームを「送信中」の状態に変更する
Update.with (FD.run formDecoder)
[ Update.onErr
[ \_ -> showError
]
, Update.onOk
[ submit
, \_ -> makeFormBusy
]
]
処理の流れは、ビジネスロジックやUIの変更にともなって日々変化するものです。それをそのまま表現できるということは、つまりアプリケーション開発において強力な武器になるということです。
従来のupdateへの変換
どんなに上手に処理の流れを記述できても、そのupdate関数がThe Elm Architectureで使えなくては意味がありません。そしてそれは、Update.run
関数を使うことで可能になります。
run : Update model msg -> model -> ( model, Cmd msg )
Browser.element
などにupdate関数を渡すときに少し工夫するだけです。
main : Program flags model msg
main =
element
{ init = init
, view = view
, update = Update.run << update
, subscriptions = subscriptions
}
update : msg -> Update model msg
update = Debug.todo "update function with `Update`"
たったこれだけでTEAの中に組み込めるのです。
まとめ
このように、elm-update-builderを使うことで処理の流れを分かりやすく表現できるようになります。ぜひ使ってみてください。
さくらちゃんのツイッターをフォローする
さくらちゃんが書いた他の記事を見る
さくらちゃんが翻訳したElmの本を手に入れる
さくらちゃんの写真集を手に入れる