LoginSignup
7
3

More than 5 years have passed since last update.

Elm 0.18 入門 (3)アクションの連鎖

Last updated at Posted at 2017-08-31

前回は簡単なフォームを作成して、入力をモデルの状態に反映させてみました。

今回はちょっとアプリケーションのフレームワークを高度なものに変更して、アクションを起点に別のアクションを発生させてみましょう。

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)

modelCmd msg のリストを受け取って、 initModel が返したい型である (model, Cmd msg) を返します。
今回は List (Cmd Msg) には空のリストを渡しておきましょう。

update : 更新関数の変更

まずは updateinitModel と同じように変更しましょう。

-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 を呼び出す処理になります。
つまり、 initModelupdate の返り値で Cmd Msg に何らかのアクションが指定するとそのアクションが実行されます。
ただし、 MsgCmd Msg は異なるため、 model ! [ Increase 1 ] などとしてもコンパイルエラーになります。
ではどうすれば良いのでしょうか?

Task を評価して Cmd を得る

Cmd msg を作る方法は Task を作って Task.performTask.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://gyazo.com/d709e14aa2913dd51a326fca606496f9

ココまでのコード -> https://ellie-app.com/4b78MtJKJZka1/3

まとめ

今回は複数のアクションが連鎖したり初期化時にアクションを発行するケースに対応するため、アプリケーションのフレームワークをより高度なものに変更しました。
次回は subscriptions も利用してバックグラウンドで自動実行されるタスクを作ってみましょう。

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3