LoginSignup
11
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-03-17

概要

  • elm 0.19のSPAでページ分割のことがよく分からなかった
  • 特に、モジュール間でのメッセージやモデルの変換で躓いた
  • elm本の写経をしながら理解する
  • Cmd.mapHtml.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.mapmap : (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.mapCmd.mapと同様に、map : (a -> msg) -> Html a -> Html msgとなっている。
Page.Repo.view repoPageModelの返すHtml Page.Repo.Msgは、
|> Html.map RepoMsgによって、Html Msgに変換される。

参考

Elmでviewを分割
Cmd.map
Html.map
コンストラクタ
レコードのコンストラクタ

11
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
3