以下の内容は「僕はこうしているよ」っていう提案なので常に Maybe
を使ったほうがいいよっていう主張ではありません。
むしろ「こういうときには Maybe
使わないようがいいよ」みたいな情報をいただければ、よりよい内容に進化させられるのでぜひご協力くださいm(_ _)m
概要
Elm は input タグの value
を取得する方法を持たない1ため、Model
に input タグに入力された値を保持して、入力があるたびに input
イベントや change
イベントで Model
の値を更新して管理します。
type alias Model =
{ inputEmail : String
}
init : ( Model, Cmd Msg )
init =
( { inputEmail = ""
}
, Cmd.none
)
type Msg
= InputEmail String
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
InputEmail email ->
( { model
| inputEmail = email
}
, Cmd.none
)
view : Model -> Html Msg
view model =
div
[]
[ text "email"
, input
[ type_ "email"
, onInput InputEmail
, value model.inputEmail
]
[]
]
この記事では、input の値を保持する際に Maybe
を使うことの利点を示します。
つまり、上記の例を以下のように書き換えるとどんな嬉しいことがあるかをご説明します。
type alias Model =
{ inputEmail : Maybe String
}
init : ( Model, Cmd Msg )
init =
( { inputEmail = Nothing
}
, Cmd.none
)
type Msg
= InputEmail String
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
InputEmail email ->
( { model
| inputEmail = Just email
}
, Cmd.none
)
view : Model -> Html Msg
view model =
div
[]
[ text "email"
, input
[ type_ "email"
, onInput InputEmail
, value <| Maybe.withDefault "" model.inputEmail
]
[]
]
利点1: 未入力であることを明示できる
Maybe
を使うことで、その入力欄が
- まったく入力されていない状態なのか (
Nothing
) - 一度入力して、あとで空白に戻した状態なのか (
Just ""
)
を別の値で表現できます。
これができると、いろいろな応用がしやすくなります。
応用例: 初期状態では必須項目に「入力されていません」と表示しないUI
必須項目に入力がないときに、「入力されていません」と表示されるUIはよく見かけますが、
ページを開いた瞬間から表示されていると
「宿題やろうと思ってたのに、お母さんが『宿題やったの?』って聞いてくるからやる気なくなったじゃんか!」
っていう小中学生のころのあの気持ちを思い出してしまいます。
そこで、EFO (Entry Form Optimization) などでよく推奨されるのが、
- ページを開いた瞬間には必須入力のエラーを表示しない
- 送信ボタンを押したり、一度入力して空白に戻した時にはエラーを表示する
という挙動です。
- まだ何もいじっていない状態 (
Nothing
) - 一度入力して空白に戻した状態 (
Just ""
)
の2つを Model
の状態として明確に区別できていると、こういったUIが実現しやすくなります。
将来の拡張性
もちろん、必ずしも毎回そのようなUIになるとは限りませんし、
Elm は型とコンパイラに守られた言語なので、最初は Maybe
を使わずに書いて、あとで必要になった時に Maybe
を使うように書き換えてもデグレはほとんど起きないでしょう。
将来の拡張性のために今のうちに Maybe
を使っておくか、YAGNI的にまずは Maybe
なしで始めるか、状況によって使い分けるのが現実解になると思います。
利点2: lazy
と併用しても正しく動く
input
や textarea
はタグ自体が内部状態を持っているため lazy と合わせて使うと意図しない挙動を示します。
詳しい説明はリンク先に譲りますが、全角で入力された内容を自動で半角に変更(僕はNormalizationと呼んでます)したりすると、入力欄に表示されている内容と Model
に保持されている内容に齟齬が生じます。
リンク先の記事では独自に定義した専用の IString
の仕様を提案していますが、実は単純に Maybe String
に変更するだけでも IString
と同じ効果があります。
記事への反応
「むしろ Maybe
じゃ足りないこともある」という意見もいただきました。
「未入力」と「入力済み空欄」をきちんと区別できるので最低限必須だと思ってる。最近Elm実装でAutocompletionを提供するにあたって、2状態じゃ足りなくなったので(「Autocompleによる入力済み」が欲しくなった)3状態Union typeを作って対応したこともあった https://t.co/1GhQNOiLgk
— Gadagarr (@gada_twt) 2018年4月17日
Model
はあくまでも現在の状態を保持するものなので、見た目上同じに 見える からといって同じ状態にしてしまうのではなく、Union Type を活用して Model
上はできるだけ informative にすることで、結果的に変なフラグなども不要になってシンプルに拡張性高く記述することができると思います。
まとめ
lazy
と併用し、Normalization を行う場合、「利点1」も加味して考えるのであれば、最初から Maybe String
などを使うと便利です。
それ以外の場合は、状況に応じて Maybe
を使う選択肢も視野に入れて設計するとあとあと困ることが少なくなると思います。
-
実際にはportを使ったりすればできますが、推奨するやり方ではないのでここでは無視します。 ↩