TODOリストを作る部 その4

nekoroll.hatenablog.com

の続き
基本的な構文とかは慣れてきたのでjavascriptとの通信を行う
portを扱ってみる
guide.elm-lang.jp

ellie-app.com

やること

  • [x] ListでTODOリストを保持する
  • [x] ListをHTMLに吐き出す
  • [x] inputに入力した値をbuttonを押すことでリストに追加
  • [x] TODO横に削除ボタンを作成、押したら消せるように
  • [x] TODOをダブルクリックでinput化して更新できるように
  • [x] localstorageに保存できるようにする
  • [ ] 見た目オシャンティーにする

Browser.elementに突入

平穏だったsandboxの地を離れ新世界element
ここは副作用という魑魅魍魎が跋扈する異世界である
f:id:nekorollykk:20200902002033p:plain
さながら気分はFF7ワールドマップ解放後

sandboxからelement

Browser - browser 1.0.2

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



驚くほどわかりやすい記事を読みあっさり理解
「そういうもんか」で進める事が出来る脳みそに進化していたのでハマらずに済んだ やったね

Cmd

外部とのやり取り(副作用-javascriptとか乱数とか)を扱う仕組み
initupdateのみで使用可能なのがミソ

Subscription

今回は特にやることなし!

subscriptionはもっと楽でグローバルな ブラウザのイベントや一定時間毎に何かをするたかだか扱う副作用です

なので今後使う予定

subscriptions

modelを受け取りSub msgを返す
今回は特に使わないので無名関数にする

, subscriptions = \_ -> Sub.none

update

msgmodelを受け取り(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 )
  1. 関数の戻り値がModelから(Model, Cmd msg)になった
  2. VariantのパターンマッチでCmd.none(何もしない)を返すようにした
  3. 終わり

後々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でもこれが出来たらすごい嬉しい
  • いつも言っているけどエラーが親切
  • initupdateでしか副作用を扱えないのは扱いやすい
    副作用だらけになってどこで何を操作しているのかわからなくなるケースを無くせる

PHPだったりjavascriptだったり自由度が高い言語触ってきたけど
このぐらい制限ある方が凄く楽しく書けるなーと思いました

今後

  • initlocalstorageから読み出すようにする
    データが有ればデータ、無ければ[]Maybeを使って書けるはず
  • デザイン当てる
  • TODO終わったらゲーム作る