おさらい
Elmアーキテクチャについては、以下の記事で簡単に紹介しました。
ViewからUpdateが発行されたらModelが変更され、Viewが再レンダリングされるという世界ですね。
今回は、ElmアーキテクチャにEffectを盛り込んでいきます。
この記事は、The Elm Architecture + Effects に沿って進めていきます。
Effectsとは
- Commands
- Subscriptions
Effectsとは上記の機能です。これらの機能を用いることで、Elmのコンポーネントが外部の世界とつながりをもつことができます。
SubscriptionsはElm0.17の目玉機能でもありますね。
Commandsとは
何かしらのEffectを要求するものらしいです。
乱数を求めたり、HTTPリクエストを発行するのがCommandの役割だとか。実行結果は外部要因によって変化しえます。
Subscriptionsとは
Subscriptionsとは、Geolocationの変化や、WebSocket経由でのメッセージ受信のようなイベントを待ち、それらが起こると更新処理が走るような仕組みです。
アーキテクチャのスケルトンを拡張
Effectsを盛り込んだアーキテクチャスケルトン(骨組み)は、以下のようになります。
-- Model
type alias Model =
{ ...
}
-- Update
type Msg = Submit | ...
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
...
-- View
view : Model -> Html Msg
view model =
...
-- Subscriptions
subscriptions : Model -> Sub Msg
subscriptions model =
...
-- Init
init : (Model, Cmd Msg)
init =
...
Subscriptionsやinitが追加されたり、updateの結果にCmdというものが増えたのがわかります。
以下、詳しく見ていきます。
update関数
update関数は、新しいModelだけでなく、実行したいコマンドも返すようになりました。これらのコマンドはすべて、Msgを生成しますが、Msgはupdate関数へと送られます。(Msgはアクションを識別する名前のようなものでした。)
subscriptions関数
subscriptions関数では、現在のModelの変更を監視するためのイベントソースを宣言します。
Html MsgやCmd Msgのように、subscriptionsもupdate関数に渡すためのMsgを生成します。
init
今回は、initは単なる初期Modelです。(Reduxで言うところのinitialState)
update関数のように、ModelとCommandsを提供します。ここでは、初期化のために値を渡したり、最初のHTTPリクエストを発行するような、初期化に必要なすべてのことを行います。
いまはまだこれらのことが分かっていなくても大丈夫です。実際の動きを見てみましょう。
Rolls Dice
まずは、1~6の数値をランダムに生成するような”rolls dise”というアプリケーションを作成します。
ここでは、最小構成をつくるフェーズと、詳細を埋めていくフェーズの2つに分けて進めていきます。
フェーズ1 最小構成
まずはModelです。ModelはシンプルにInt型のデータを1つ持ちます。
-- Model
type alias Model =
{ dieFave : Int
}
次にViewを書きます。
-- View
view : Model -> Html Msg
view model =
div []
[ h1 [] [ text (toString model.dieFace)]
, button [ onClick Roll ] [ text "Roll" ]
]
次にUpdateを埋めていきます。
-- Update
type Msg = Roll
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Roll ->
(model, Cmd.none)
Elmアーキテクチャを学んだ時と異なり、update関数はModelだけでなくCmdも返しています。
ただし、今のところは、Cmd.none(なにもしない)をセットしておきます。
この部分が「ランダムな数値を取得する」に置き換えられるわけですね。
最後に、initをつくります。
init : (Model, Cmd Msg)
init =
(Model 1, Cmd.none)
ちなみに、subscriptionsはまだ使わないので、以下のようにしておきます。
-- Subscriptions
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
なぜ使わないのに、subscrptionsを定義するかというと、いわばnot nullのようになっているからです。
最後に、ファイルの先頭にimportやスタート記述を書きます。
現時点でのファイルは、以下のようになっているはずです。
import Html exposing (..)
import Html.App as App
import Html.Events exposing (..)
import Random
main =
App.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions -- ←App.programはsubscriptionを要求する。
}
-- Model
type alias Model =
{ dieFace : Int
}
-- Update
type Msg = Roll
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Roll ->
(model, Cmd.none)
-- View
view : Model -> Html Msg
view model =
div []
[ h1 [] [ text (toString model.dieFace)]
, button [ onClick Roll ] [ text "Roll" ]
]
-- Subscriptions
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- Init
init : (Model, Cmd Msg)
init =
(Model 1, Cmd.none)
これでフェーズ1は終了です。
コンパイルが通るので、ブラウザで確認ができます。やりましたね!
フェーズ2 完成
乱数発生周りが大きく欠けている状態なので、そこを埋めます。
まずはupdateを追加します。
-- Update
type Msg
= Roll
| NewFace Int -- New!
NewFace関数が呼ばれると、Elmが乱数を渡してくれます。
関数部分も適用したupdateがこちら。
-- Update
type Msg
= Roll
| NewFace Int
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Roll ->
(model, Random.generate NewFace (Random.int 1 6))
NewFace ->
(Model newFace, Cmd.none)
いくつかの変更が行われていますね。
- NewFaceメッセージ(Msg)を受け取ったパターンを追加しました。Modelを渡して、Commandは何も実行しません。
- Rollに変更があります。Randomライブラリの持つ関数を2つ使っていますね。
大切なのは、Random.generateです。
Random.generate : (a -> msg) -> Random.Generator a -> Cmd msg
Random.generate関数は2つの引数を取ります。1つ目が乱数をタグ付けするための関数です。今回は、Intを取り、Msgを返すNewFace関数を渡しました。
そして、2つ目の引数は”generator”です。
generatorはIntだけでなく、FloatやStringの値も生成できるようです。
Random.int : Int -> Int -> Random.Generator Int
これで、要求通りに機能するアプリケーションができました。
ここまでの学びは以下のとおりです。
- プログラムは少しずつ書きすすめること。シンプルなスケルトンからはじめて、徐々にタフな機能をつけていく。
- update関数は、いまや新しいModelとCommandを生成します。
- すぐにランダム値を取得することはできません。Commandを生成して、Elmに生成させるのです。
完成したコードは以下のとおりです。
import Html exposing (..)
import Html.App as Html
import Html.Events exposing (..)
import Random
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
-- Model
type alias Model =
{ dieFace : Int
}
-- Update
type Msg
= Roll
| NewFace Int
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Roll ->
(model, Random.generate NewFace (Random.int 1 6))
NewFace newFace ->
(Model newFace, Cmd.none)
-- View
view : Model -> Html Msg
view model =
div []
[ h1 [] [ text (toString model.dieFace)]
, button [ onClick Roll ] [ text "Roll" ]
]
-- Subscriptions
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- Init
init : (Model, Cmd Msg)
init =
(Model 1, Cmd.none)
長くなってしまったので、ここでいったん切ります。
次回は、HTTP、時間ごとの処理、アニメーション、WebSocketsについて学んでいきます。
最後にもう1度、本家のリンクを貼っておきます。
何か間違いなどがありましたら、コメントでお知らせください。