の続き
基本的な構文とかは慣れてきたのでjavascript
との通信を行う
port
を扱ってみる
guide.elm-lang.jp
やること
- [x] ListでTODOリストを保持する
- [x] ListをHTMLに吐き出す
- [x] inputに入力した値をbuttonを押すことでリストに追加
- [x] TODO横に削除ボタンを作成、押したら消せるように
- [x] TODOをダブルクリックでinput化して更新できるように
- [x] localstorageに保存できるようにする
- [ ] 見た目オシャンティーにする
Browser.element
に突入
平穏だったsandbox
の地を離れ新世界element
へ
ここは副作用という魑魅魍魎が跋扈する異世界である
さながら気分はFF7ワールドマップ解放後
sandbox
からelement
へ
Browser.element { init : flags -> ( model, Cmd msg ) , view : model -> Html msg , update : msg -> model -> ( model, Cmd msg ) , subscriptions : model -> Sub msg }
一瞬面食らったがそこにABAB↑↓BA先生のスーパーフォロー
いつもありがとうございますm( )m
— ABAB↑↓BA (@ababupdownba) 2020年9月1日
Cmdは外部とのやり取り(副作用)を扱うもので、initとupdateでのみ副作用を扱えるんですね!
— 🐊すがわに🐊 (@nek0roll) 2020年9月1日
わかりやすい記事ありがとうございます(^o^)
ですね!
— ABAB↑↓BA (@ababupdownba) 2020年9月1日
そこを押さえれば、elmがCmdで扱えるものの種類なんて数えるほどしか無いので その扱いを抑えるだけで終了です。
subscriptionはもっと楽でグローバルな ブラウザのイベントや一定時間毎に何かをするたかだか扱う副作用です
驚くほどわかりやすい記事を読みあっさり理解
「そういうもんか」で進める事が出来る脳みそに進化していたのでハマらずに済んだ やったね
Cmd
外部とのやり取り(副作用-javascript
とか乱数とか)を扱う仕組み
init
とupdate
のみで使用可能なのがミソ
Subscription
今回は特にやることなし!
subscriptionはもっと楽でグローバルな ブラウザのイベントや一定時間毎に何かをするたかだか扱う副作用です
なので今後使う予定
subscriptions
model
を受け取りSub msg
を返す
今回は特に使わないので無名関数にする
, subscriptions = \_ -> Sub.none
update
msg
とmodel
を受け取り(model, Cmd msg)
のtuple
を返す
まずは既存のupdate処理に手を加えてあげる
update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of Add -> case String.isEmpty model.inputText of True -> ( { model | errorMessage = TODO_IS_EMPTY }, Cmd.none ) False -> ( { model | todoList = Todo (getNextTodoIndex model.todoList) model.inputText False :: model.todoList , inputText = "" , errorMessage = NO_ERROR } , Cmd.none ) UpdateTextField txt -> ( { model | inputText = txt }, Cmd.none ) Delete id -> ( { model | todoList = filterByIndex model.todoList id }, Cmd.none ) Edit id isEditting -> ( { model | todoList = setEdittingByIndex model.todoList id isEditting }, Cmd.none ) Update id newTodo -> ( { model | todoList = updateByIndex model.todoList id newTodo }, Cmd.none )
- 関数の戻り値が
Model
から(Model, Cmd msg)
になった - 各
Variant
のパターンマッチでCmd.none(何もしない)
を返すようにした - 終わり
後々localstorage
への保存処理とかが入るかもしれないけど、まずは動くようにする
init
flags
を受け取り(model, Cmd msg)
を返す
flags
は使わないので()
にして、現状副作用も扱わないのでCmd.none
を返す
init : () -> ( Model, Cmd Msg ) init _ = ( { todoList = [] , inputText = "" , errorMessage = NO_ERROR } , Cmd.none )
まずはこれだけで下準備完了
sandbox
と同様に動くようになった
localstorage
へのアクセス
module宣言
port module Main exposing (main)
modeule
宣言の頭にport
をつける
localstorage
にアクセスするport
の作成
port persistence : List Todo -> Cmd msg
javascript
側にlocalStorage
へアクセスするAPIを定義
app.ports.persistence.subscribe(function(data) { localStorage.setItem('todoList', JSON.stringify(data)); });
Elm
と外部の処理が分離できる- APIの仕様が変わってもElm側はほとんど影響を受けず
Elm
側は保存の仕様を意識しなくていい
という点に気づいて、素晴らしい仕組みだなと思った
ちなみにこの段階だとコンソールに
Uncaught TypeError: Cannot read property 'persistence' of undefined
が吐かれている
これはport
を定義しているけどElm
側で一度も呼び出されていないため 親切
保存処理の実装
update
毎に常に保存できるようにする
update
毎に永続化する関数
persistenceEvenryUpdate : Msg -> Model -> ( Model, Cmd Msg ) persistenceEvenryUpdate msg model = let ( updatedModel, cmd ) = update msg model in ( updatedModel, persistence updatedModel.todoList )
https://elm-lang.org/docs/syntax#let-expressions
let in
構文で値を展開しつつその後の処理に使えることを知ったので
update
処理を動かし、その後更新されたmodel
を使ってlocalstorage
に保存するようにした
Browser.element
main : Program () Model Msg main = Browser.element { init = init , view = view , update = persistenceEvenryUpdate , subscriptions = \_ -> Sub.none }
元々update
呼んでいた部分をpersistenceEvenryUpdate
に置き換えただけ
update
側のCmd
は常にCmd.none
を返すようにした
これで終わりである
model
には一切手を加えていないview
には一切手を加えていないupdate
はラップする関数を作っただけ
責務が分かれているからかビックリするほどキレイに進んだ
まとめ
- キレイに責務が分かれていると修正が物凄い簡単
普段やっているPHP
でもこれが出来たらすごい嬉しい - いつも言っているけどエラーが親切
init
とupdate
でしか副作用を扱えないのは扱いやすい
副作用だらけになってどこで何を操作しているのかわからなくなるケースを無くせる
PHP
だったりjavascript
だったり自由度が高い言語触ってきたけど
このぐらい制限ある方が凄く楽しく書けるなーと思いました
今後
init
でlocalstorage
から読み出すようにする
データが有ればデータ、無ければ[]
がMaybe
を使って書けるはず- デザイン当てる
- TODO終わったらゲーム作る