前回は簡単なフォームを作成して、入力をモデルの状態に反映させてみました。
今回はちょっとアプリケーションのフレームワークを高度なものに変更して、アクションを起点に別のアクションを発生させてみましょう。
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 も利用してバックグラウンドで自動実行されるタスクを作ってみましょう。
