Elm --- 階層化

下記記事にてElmを用いてカウンタを実装しました。
Elm --- Model、View、Update - 何でもプログラミング

今回はこのカウンタを再利用して、複数のカウンタを配置してみます。

内容はElmのTutorialにあるものとほとんど同じです。

作成するアプリケーション

カウンタが2つあり、一番下に合計値が出力されるアプリケーションになります。
f:id:any-programming:20170302193737p:plain

Counter.elm

新たにCounter.elmファイルを作成し、下記コードを記述します。

内容は上記記事のものとほぼ同じです。

VisualStudioCodeを利用しているのですが、関数の型宣言をしないと警告が出るようになっていました。

module Counter exposing (..)

import Html exposing (Html, div, button, text)
import Html.Events exposing (onClick)

type alias Model = { count : Int }

initialModel : Model
initialModel = { count = 0 }

type Msg 
    = Increment 
    | Decrement

update : Msg -> Model -> Model
update msg model =
  case msg of
    Increment ->
      { model | count = model.count + 1 }

    Decrement ->
      { model | count = model.count - 1 }

view : Model -> Html Msg
view model =
  div []
    [ button [ onClick Decrement ] [ text "-" ]
    , div [] [ text (toString model.count) ]
    , button [ onClick Increment ] [ text "+" ]
    ]


Main.elm

CounterのModel、update、viewをそのまま利用しています。

Html.App.mapを利用して、CounterのメッセージをMainの方に伝搬しています。

beginnerProgramがいつの間にかHtml.Appに移動していました。

import Html exposing (Html, div, text)
import Html.App exposing (beginnerProgram, map)
import Counter

type alias Model = 
    { counter1 : Counter.Model 
    , counter2 : Counter.Model
    }

initialModel : Model 
initialModel =
    { counter1 = Counter.initialModel
    , counter2 = Counter.initialModel
    }

type Msg 
    = Counter1Msg Counter.Msg 
    | Counter2Msg Counter.Msg

update : Msg -> Model -> Model
update msg model =
  case msg of
    Counter1Msg x ->
      { model | counter1 = Counter.update x model.counter1 }

    Counter2Msg x ->
      { model | counter2 = Counter.update x model.counter2 }

view : Model -> Html Msg
view model =
  div []
    [ map Counter1Msg (Counter.view model.counter1)
    , map Counter2Msg (Counter.view model.counter2)
    , div [] [ text (toString (model.counter1.count + model.counter2.count)) ]
    ]

main : Program Never
main =
  beginnerProgram 
  { model = initialModel
  , view = view
  , update = update 
  }


主要部分

// CounterのModelを保持
type alias Model = 
    { counter1 : Counter.Model 

// メッセージの一つをCounterのMsg型に
type Msg 
    = Counter1Msg Counter.Msg 

// CounterのMsg型が来たらCounterのupdateを実行
update msg model =
  case msg of
    Counter1Msg x ->
      { model | counter1 = Counter.update x model.counter1 }

// Counterのviewを利用し、mapにてメッセージを受け取り
view model =
  div []
    [ map Counter1Msg (Counter.view model.counter1)


階層化よりコントロール

公式でもあまりネストさせるのは推奨しておりません。

Mainの方で子要素のupdateやmsgを操作するよりは、Htmlのコントロール化(今回であればHtml.counterみたいなのを準備)したほうがフラットになってよい気がします。