前回の記事で、Browserモジュールを使って「Hello World!!」しました。
んで、前回のはモデルを初期化(init関数)してページを描画(view関数)しただけでした。
なので今回は、さらにページにアクションを起こして更新処理(update関数)を行うまでをやりたいと思います。
前回のプログラムはあえてBrowserモジュールを使ったものでしたが、今回はBrowserモジュールは必要です。
前回のはただHTMLを出力すればいいだけのものだったので本当はわざわざモデルを定義する必要すらなかったのですが、今回のはモデルを定義して更新していくという仕組みを使うためBrowserモジュールの力が必要になってきます。
ElmでBrowserモジュールを使って「Hello World!!」する
今回作ったもの
「Hello World!!!!!!」です。
叫ぶのです。前回よりビックリマーク多めです。
まぁ具体的には「Hello World」という文字列がボタンを押すと「Hello World!!!!!!」になるというプログラムです。
完成品はこちら
まずは完成品です。ここ(Try Elm!)で貼り付けて動作を試せます。
-- Browserモジュール
import Browser
-- HTMLタグを出力するためのモジュール
import Html exposing (..)
-- イベントを起こすためのモジュール
import Html.Events exposing (..)
-- MAIN
-- メイン関数
main =
Browser.sandbox
{ init = init
, update = update
, view = view
}
-- MODEL
-- モデルを定義
type alias Model =
{ hello : String }
-- モデルを初期化
-- model.helloに"Hello World"を設定する
init : Model
init = Model "Hello World"
-- UPDATE
-- メッセージを定義
-- ページ更新命令を定義
type Msg
= Shout String
-- ページ更新処理を定義
-- ページ更新時にmodel.helloにmsgのhelloを設定する
update : Msg -> Model -> Model
update msg model =
case msg of
Shout newHello ->
{ model | hello = newHello }
-- VIEW
-- ページ描画処理を定義
-- この例では、model.helloを表示し、"Shout!!!!!!"ボタンを表示する
-- "Shout!!!!!!"ボタンをクリックすると、"Hello World!!!!!!"を渡してページを更新する
view : Model -> Html Msg
view model =
div []
[ text model.hello
, button [ onClick (Shout "Hello World!!!!!!") ] [ text "Shout!!!!!!" ]
]
解説
モジュールの読み込み
前回から増えたのはこちらの読み込みです。
import Html.Events exposing (..)
これはイベント関数をまとめたモジュールです。
onClickイベントやonChangeイベントなどいろいろなイベントを付与することができる。
メイン関数の定義
メイン関数は前回と一緒なので割愛。
モデルの定義と初期化
ここも前回とほぼ一緒です。
ちなみに初期値はビックリマーク無しにしています。
init : Model
init = Model "Hello World"
更新処理の定義
今回は更新処理も動きます。
なので更新処理のパターンと処理内容を定義します。
type Msg
= Shout String
今回はmodel.helloに入っている "Hello World" を "Hello World!!!!!!" に変えたいです。
なのでmodel.helloに入っている文字列を変更する処理を定義します。それが Shout String
です。
これはメッセージに「Shoutという名前でString型の値を持ったオブジェクト」がやってきますよーという感じのことを定義しています。
この「String型の値」というのに後々 "Hello World!!!!!!" が入ってくるわけですね。
余談ですが、Elmのtype宣言はHaskellのtype宣言とは役割が違うみたいですね。
むしろ、Haskellのdata宣言と同じ役割らしいです。Haskellと一緒に勉強してる方はご注意を。
update : Msg -> Model -> Model
update msg model =
case msg of
Shout newHello ->
{ model | hello = newHello }
ここでは実際の更新処理の中身を定義しています。
前回説明したようにupdate関数はメッセージ(Msg)とモデル(Model)を受け取って、受け取ったメッセージに基づく処理を行い、新たなモデルを生成するという処理を行います。
case msg of 〜
の部分でメッセージの内容ごとに更新処理を定義しています。
Shout newHello ->
の部分がさっきMsgに定義した Shout String
ですね。Shoutの持つ「String型の値」には newHello
という名前を当てています。
そして、{ model | hello = newHello }
の部分ですが、model.helloにnewHelloの値を代入しているような感じですよね。でも実際には代入では無いんです。
関数型に慣れていない人間としてはmodel.helloにnewHelloを代入しているというイメージの方がピンときやすいですが、関数型では代入はありません。
ここでは古いmodelを破棄して、model.helloにnewHelloの中身と同じ文字列が入った状態の新しいmodelを生成しているというのが正しい動きです。
ページ描画処理の定義
最後にページ描画処理です。
前回よりも少し複雑になりました。
view : Model -> Html Msg
view model =
div []
[ text model.hello
, button [ onClick (Shout "Hello World!!!!!!") ] [ text "Shout!!!!!!" ]
]
div関数を例に説明すると、 div [] [ text "Test!!" ]
と書くと <div>Test!!</div>
という出力が得られます。
div関数の最初の引数は属性で、2つ目の引数はタグの中身です。また、タグの中身に複数のタグを入れる場合にはカンマで区切ります。
Htmlモジュールの関数は大体この形になってます。
その要領で考えると今回の例の出力はこんな感じになります。 (model.helloの中身が "Hello Warld" の場合)
<div>
Hello Warld
<button>Shout!!!!!!</button>
</div>
onClick (Shout "Hello World!!!!!!")
はどこ行った?という感じですが、イベントは属性の方には出力されません。
そしてこの onClick (Shout "Hello World!!!!!!")
が更新のトリガーになります、大体見ての通りですがクリックしたら Shout "Hello World!!!!!!"
をメッセージとして送り、更新処理を行うというものです。
ここでShoutの値として "Hello World!!!!!!" が与えられています。
全体の流れ
まずは、init関数が呼ばれて初期状態のモデルが生成されます。
model.helloの中身は "Hello World" です。
init : Model
init = Model "Hello World"
次に、view関数によって現在のモデルを元にHTMLが描画されます。
この時点での文字列の表示は "Hello World" となっています。
そして "Shout!!!!!!" というボタンも表示されます。
view : Model -> Html Msg
view model =
div []
[ text model.hello
, button [ onClick (Shout "Hello World!!!!!!") ] [ text "Shout!!!!!!" ]
]
次は、ユーザーの操作をトリガーにしてupdate関数が実行されます。
ユーザーの操作とは "Shout!!!!!!" ボタンのクリックです。
"Shout!!!!!!" ボタンのクリックによって Shout "Hello World!!!!!!"
というメッセージと現在のモデルがupdate関数に送られます。
update関数は、古いモデルを破棄して、 "Hello World!!!!!!" という文字列がmodel.helloに設定された新しいモデルを生成します。
update : Msg -> Model -> Model
update msg model =
case msg of
Shout newHello ->
{ model | hello = newHello }
そして次に、再びのview関数です。
view関数によって新しいモデルを元にHTMLが描画されます。
ここで文字列の表示が "Hello World!!!!!!" に変わります。
"Shout!!!!!!" ボタンに関しては特にモデルの値を参照していないので更新前と同じものが出力されます。
view : Model -> Html Msg
view model =
div []
[ text model.hello
, button [ onClick (Shout "Hello World!!!!!!") ] [ text "Shout!!!!!!" ]
]
文字列が "Hello World!!!!!!" に変わった後も "Shout!!!!!!" ボタンをクリックすればupdate関数は呼ばれます。
ですが、すでに "Hello World!!!!!!" になっているので表示上変化はありません。
まとめ
今回のプログラムはあえてBrowserモジュールを使っているわけではなく、必要性があって使っています。
なので、Browserモジュールを使うか否かはページの状態が更新される必要があるかどうかという基準で良いのかなーと思います。大概の場合ページの更新は必要だと思いますが。
やはり記事を書きながらもいろいろ気付きがありますね。アウトプット良いです。
なるべく正しい情報を書くようによく調べて書いてはいますが、まだ理解不足の面もあるかもしれません。
指摘いただけるとありがたいです。