概要
- elm 0.19のSPAでページ分割のことがよく分からなかった
- 特に、モジュール間でのメッセージやモデルの変換で躓いた
- elm本の写経をしながら理解する
- Cmd.map と Html.map の理解が必要。
-
type Msg = RepoModel Page.Repo.Model
ってやるとRepoModel: Page.Repo.Model -> Msg
って関数が暗黙的に作られるのってelm本買ってない人は何処で習うの?
環境
- Windows10
- virtualbox 6.0.4
- vagrant 2.2.4
- Ubuntu 18.04.1 LTS (GNU/Linux 4.15.0-29-generic x86_64)
- Docker version 18.09.2, build 6247962
- docker-compose version 1.23.2, build 1110ad01
ソース
理解のために
モデルの受け渡し
下記のように、モジュールで定義したモデルを呼び出し側で使う方法がよく分かっていなかった。
Page/Repo.elm
module Page.Repo exposing (Model, Msg, init, update, view)
type alias Model =
{ userName : String
, projectName : String
, state : State
}
まず、モジュールのモデルを受け取れる型を作る。
Main.elm
module Main exposing (Model, Msg(..), init, main, subscriptions, update, view, viewLink)
--
-- ~ 省略 ~
--
+ import Page.Repo exposing (..)
--
-- ~ 省略 ~
--
type alias Model =
{ key : Nav.Key
, page : Page
}
type Page
= NotFound
| ErrorPage Http.Error
| TopPage
| UserPage (List Repo)
- | RepoPage (List Issue)
+ | RepoPage Page.Repo.Model
updateでの受け渡し
更新時に、モジュールのupdateを呼ぶようにする
src/assets/js/Page/Repo.elm
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
--- init での Http リクエストの結果が得られたら Model を変更する
case msg of
GotIssues (Ok issues) ->
( { model | state = Loaded issues }, Cmd.none )
GotIssues (Err err) ->
( { model | state = Error err }, Cmd.none )
Main.elm
type Msg
= UrlRequested Browser.UrlRequest
| UrlChanged Url.Url
| Loaded (Result Http.Error Page)
+ | RepoMsg Page.Repo.Msg
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
UrlChanged url ->
-- ページの初期化をヘルパー関数に移譲
goTo (Route.parse url) model
-- ページの内容を非同期で取得した時の共通処理
Loaded result ->
--
-- ~ 省略 ~
--
+ -- Repoページのメッセージが来たとき
+ RepoMsg repoMsg ->
+ -- 現在表示中のページが
+ case model.page of
+ -- RepoPageであれば、
+ RepoPage repoModel ->
+ -- Repoページのupdate処理を行う
+ let
+ ( newRepoModel, topCmd ) =
+ Page.Repo.update repoMsg repoModel
+ in
+ ( { model | page = RepoPage newRepoModel }, Cmd.map RepoMsg topCmd )
+
+ _ ->
+ ( model, Cmd.none )
ここで、Cmd.map RepoMsg topCmd
は、Mainモジュールのupdate
とRepoモジュールのPage.Repo.update
の型が合わないことを解決するために使用している。
以下のように型があっていない
update: Msg -> Model -> ( Model, Cmd Msg )
Page.Repo.update : Page.Repo.Msg -> Page.Repo.Model -> (Page.Repo.Model, Cmd Page.Repo.Msg)
Cmd.map
は map : (a -> msg) -> Cmd a -> Cmd msg
と定義されている。(*)
RepoMsg Page.Repo.Msg
は暗黙でRepoMsg: Page.Repo.Msg -> Msg
の関数を持つ。(elm本 P.66を参照 )
これにより、Page.Repo.update
が返したtopCmd
の型はCmd Page.Repo.Msg
からCmd Msg
に変換される。
ヘルパー関数での受け渡し
ここでも以下のように、モジュールのinit
を呼んだ後に、Cmd.map
を使って型を変換する
Main.elm
{- パスに応じて各ページを初期化する -}
goTo : Maybe Route -> Model -> ( Model, Cmd Msg )
goTo maybeRoute model =
case maybeRoute of
Nothing ->
--
-- ~ 省略 ~
--
Just (Route.Repo userName projectName) ->
-- Repo ページの初期化
let
( repoModel, repoCmd ) =
Page.Repo.init userName projectName
in
( { model | page = RepoPage repoModel }
, Cmd.map RepoMsg repoCmd
)
ビューの受け渡し
Page/Repo.elm
view : Model -> Html Msg
view model =
case model.state of
Init ->
text "Loading ..."
Loaded issues ->
ul [] (List.map (viewIssue model.userName model.projectName) issues)
Error e ->
text (Debug.toString e)
Main.elm
view : Model -> Browser.Document Msg
view model =
{ title = "URL Interceptor"
, body =
[ a [ href "/" ] [ h1 [] [ text "My github view" ] ]
, case model.page of
NotFound ->
viewNotFound
ErrorPage error ->
viewError error
TopPage ->
viewTopPage
UserPage repos ->
viewUserPage repos
- RepoPage issues ->
- viewRepoPage issues
+ RepoPage repoPageModel ->
+ -- Repoページのview関数を呼ぶ
+ Page.Repo.view repoPageModel
+ |> Html.map RepoMsg
+ ]
}
Html.mapもCmd.mapと同様に、map : (a -> msg) -> Html a -> Html msg
となっている。
Page.Repo.view repoPageModel
の返すHtml Page.Repo.Msg
は、
|> Html.map RepoMsg
によって、Html Msg
に変換される。