前回は簡単なフォームを作成して、入力をモデルの状態に反映させてみました。
今回はちょっとアプリケーションのフレームワークを高度なものに変更して、アクションを起点に別のアクションを発生させてみましょう。
beginnerProgram
から program
へ
それではまずビギナーを卒業しましょう。
これまで使っていた Html.beginnerProgram
のシグネチャはこちらでした。
beginnerProgram :
{ model : model
, view : model -> Html msg
, update : msg -> model -> model
} -> Program Never model msg
一方、 Html.program
はこうなります。(比較のために公式ドキュメントより項目の順序を少し変更しています)
program :
{ init : (model, Cmd msg)
, view : model -> Html msg
, update : msg -> model -> (model, Cmd msg)
, subscriptions : model -> Sub msg
} -> Program Never model msg
-
init
(model
だった部分) の型にCmd msg
が追加されたタプルになっている -
update
の最後の返り値も同じく型が変わっている -
subscriptions
が追加されている -
Cmd
,Sub
って何だろう?
などの違いや疑問点があると思います。実際にコードを変更していきながら説明していきましょう。
ただ、今回は subscriptions
は何もしません。
initModel
: 初期化関数の変更
initModel
の初期化時に (Model, Cmd Msg)
を返すようにします。
-initModel : Model
+initModel : (Model, Cmd Msg)
initModel =
{ count = 0
, countStepInput = ""
, countStepNum = 0
}
+ ! []
シグネチャの変更は当然なのですが、 ! []
という部分を追加しました。これは Cmd Msg
を追加するための便利な関数で、次のようなシグネチャを持っています。
(!) : model -> List (Cmd msg) -> (model, Cmd msg)
model
と Cmd msg
のリストを受け取って、 initModel
が返したい型である (model, Cmd msg)
を返します。
今回は List (Cmd Msg)
には空のリストを渡しておきましょう。
update
: 更新関数の変更
まずは update
も initModel
と同じように変更しましょう。
-update : Msg -> Model -> Model
+update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
NoOp ->
- model
+ model ! []
Increase num ->
- { model | count = model.count + num }
+ { model | count = model.count + num } ! []
UpdateCountStepInput s ->
- { model | countStepInput = s }
+ { model | countStepInput = s } ! []
UpdateCountStepNum num ->
- { model | countStepNum = num }
+ { model | countStepNum = num } ! []
subscriptions
の追加
例によって呼び出されても何もしない関数を書いておきましょう。次回以降にでも使い方を解説していきます。
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
main 関数
最後に main
関数でまとめ上げます。
main : Program Never Model Msg
main =
- Html.beginnerProgram
+ Html.program
{ init = initModel
, update = update
, view = view
+ , subscriptions = subscriptions
}
さて、コンパイルするとアプリケーションが動くでしょうか?
追加した機能は一切ないので、機能は元のままのはずです。
ココまでのコード -> https://ellie-app.com/4b78MtJKJZka1/1
Cmd の作成
beginnerProgram
のときは Cmd msg
という型は出てきませんでしたが、実際にはElmが内部的に使っています。
Viewから onClick (Increase 1)
などで発行していたアクションは、 Cmd (Increase 1)
という形でデータ化されて update
を呼び出す処理になります。
つまり、 initModel
や update
の返り値で Cmd Msg
に何らかのアクションが指定するとそのアクションが実行されます。
ただし、 Msg
と Cmd Msg
は異なるため、 model ! [ Increase 1 ]
などとしてもコンパイルエラーになります。
ではどうすれば良いのでしょうか?
Task
を評価して Cmd
を得る
Cmd msg
を作る方法は Task
を作って Task.perform
や Task.attempt
を使うことです。
Task
は非同期な処理などを記述して、その成否によってアクションを発行する基盤となるものです。
一例としてアプリケーションの初期化時に入力フォームの値を 5
にするアクションを発行してみましょう。
Task
を利用するのに import が必要です。
import Task
initModel
を次のように更新しましょう。
initModel : ( Model, Cmd Msg )
initModel =
{ count = 0
, countStepInput = ""
, countStepNum = 0
}
- ! []
+ ! [ Task.perform UpdateCountStepInput (Task.succeed "5") ]
countStepInput = "5"
で初期化すれば良いのでは…と思う気持ちをぐっと堪えてください。
Task.succeed "5"
は「必ず成功し "5"
を返すタスク」を表現しています。その値を受け取って、 Cmd (UpdateCountStepInput "5")
を返り値として返します。
これが実際に評価されることで、入力フォームの値が書き換わっているわけです。
これを確認するためには、DEBUGを開いて実行されているアクションを実際に確認すると良いです。
これまではクリックやキー入力に反応してアクションが実行されていましたが、アプリケーション起動時にいきなりアクションが実行されていることがわかります。
ココまでのコード -> https://ellie-app.com/4b78MtJKJZka1/4
アクションの連鎖
それでは続いて、アクションを連鎖させてみましょう。
これまでは、
- フォームに数値を入力
- updateボタンを押す
の2アクションで増加する値を更新していました。これを1アクションにしてみましょう。
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
NoOp ->
model ! []
Increase num ->
{ model | count = model.count + num } ! []
UpdateCountStepInput s ->
- { model | countStepInput = s } ! []
+ { model | countStepInput = s } ! [ Task.perform convertInputToMsg (Task.succeed s) ]
UpdateCountStepNum num ->
{ model | countStepNum = num } ! []
これで更新ボタンは不要になったので削除しましょう。
stepInput : Model -> Html Msg
stepInput model =
form []
[ input [ onInput UpdateCountStepInput, value model.countStepInput ] []
- , input [ type_ "button", onClick (convertInputToMsg model.countStepInput), value "Update" ] []
]
コンパイルして動作を確認してみましょう。
入力フォームに数値を入力すると自動的にカウンターの増加値が更新されているのが分かるでしょうか?
ココまでのコード -> https://ellie-app.com/4b78MtJKJZka1/3
まとめ
今回は複数のアクションが連鎖したり初期化時にアクションを発行するケースに対応するため、アプリケーションのフレームワークをより高度なものに変更しました。
次回は subscriptions
も利用してバックグラウンドで自動実行されるタスクを作ってみましょう。