elm 0.19 で ページ分割をしたメモ

## 概要

* elm 0.19のSPAでページ分割のことがよく分からなかった
* 特に、モジュール間でのメッセージやモデルの変換で躓いた
* [elm本][*2]の写経をしながら理解する
* [Cmd.map][*1] と [Html.map][*4] の理解が必要。
* `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

## ソース

* [全体](https://github.com/hibohiboo/garden/tree/909fc5a439ee006424e72cac755aa9471e9640c8)
* [変更点](https://github.com/hibohiboo/garden/commit/909fc5a439ee006424e72cac755aa9471e9640c8?diff=split)

## 理解のために

### モデルの受け渡し

下記のように、モジュールで定義したモデルを呼び出し側で使う方法がよく分かっていなかった。

```elm:Page/Repo.elm
module Page.Repo exposing (Model, Msg, init, update, view)

type alias Model =
    { userName : String
    , projectName : String
    , state : State
    }
```


まず、モジュールのモデルを受け取れる型を作る。

```diff: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を呼ぶようにする

 ```elm: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 )
```

```diff: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`の型が合わないことを解決するために使用している。

```elm:以下のように型があっていない
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`と定義されている。[(*)][*1]
`RepoMsg Page.Repo.Msg`は暗黙で`RepoMsg: Page.Repo.Msg -> Msg`の関数を持つ。([elm本][*2] P.66を参照 )
これにより、`Page.Repo.update`が返した`topCmd`の型は`Cmd Page.Repo.Msg`から`Cmd Msg`に変換される。

#### ヘルパー関数での受け渡し

ここでも以下のように、モジュールの`init`を呼んだ後に、`Cmd.map`を使って型を変換する

```elm: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
            )
```

## ビューの受け渡し

```elm: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)
```

```diff: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][*4]も[Cmd.map][*1]と同様に、`map : (a -> msg) -> Html a -> Html msg`となっている。
`Page.Repo.view repoPageModel`の返す`Html Page.Repo.Msg`は、
` |> Html.map RepoMsg`によって、`Html Msg`に変換される。

## 参考

[Elmでviewを分割](https://m-shaka.github.io/blog/post/elm-modulization/)
[Cmd.map][*1]
[Html.map][*4]
[コンストラクタ][*3]
[レコードのコンストラクタ][*5]

[*1]:https://package.elm-lang.org/packages/elm/core/1.0.2/Platform-Cmd#map
[*2]: https://www.amazon.co.jp/dp/B07P253FPG/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
[*3]:https://scrapbox.io/elm-jp/%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF
[*4]:https://package.elm-lang.org/packages/elm-lang/html/latest/Html#map
[*5]:https://guide.elm-lang.jp/types/type_aliases.html