ゆるふわ技術日誌

名前に反してビシバシやっていこう

elm-spa-exampleを読んでみた所感メモ #302

elm-spa-exampleのコードを読んでみた所感メモ

ElmでSPA作ってるならこれいいよと教えてもらったサンプルコード。

github.com

これを読んでみて、初心者なりに感じたことをメモする。

このメモは正確でない可能性があるので信用しないでください。また間違ったことを言っていたら教えていただけると嬉しいです。

あと順不同です。気がついた順で書いてる。

ファイル分けについて

elm-tutorialではModel・View・Update・Messageをそれぞれ別のファイルに分割してimportするという方法がとられていたけど、このサンプルではMain.elmというファイルにすべてを詰め込んでいるようだった。

以前、Elmを書いている人に聞いたときも、ファイルは分けずに書いているというような話を聞いたので、やはりこちらが主流なのか?と感じた。

確かにコードがタテに長くはなるが、余計なimportを書かなくて済むことや、全体の見通しがよくなるという効果を見込める気がしたので、次書くときはこちらの方法を使ってみようと思った。

ページの定義について

以前、ミニブログの管理画面のようなものを自作していたときに、記事一覧画面として/articlesという画面とその記事を編集する画面として/articles/:idといった2つのページを作成したくて、

type Page
    = Articles
    | Article Int

みたいなコードを書いた記憶があったが、elm-spa-exampleでは、

type Page
    = Blank
    | NotFound
    | Errored PageLoadError
    | Home Home.Model
    | Settings Settings.Model
    | Login Login.Model
    | Register Register.Model
    | Profile Username Profile.Model
    | Article Article.Model
    | Editor (Maybe Slug) Editor.Model

というような宣言がしてあった。(最終行Editorのところ)

Maybe使ってやるときれいに宣言できるっぽい。

対応するページを返す関数のEditor部分はこんな感じで実装されてた。

        Editor maybeSlug subModel ->
            let
                framePage =
                    if maybeSlug == Nothing then
                        Page.NewArticle
                    else
                        Page.Other
            in
            Editor.view subModel
                |> frame framePage
                |> Html.map EditorMsg

これは目からウロコ。

言語仕様知ってるだけじゃなくてそれが何に使えるかちゃんと知らないといかんなと思った。

Viewについて

コンポーネント指向といったらいいのか、結構部品を分けている印象を感じた。(/src/views/以下)

特にお気に入りボタンとか機能を持っているパーツは分けてあった気がする。

moduleの宣言について

前書いていたときはmoduel Hoge exposing (..)という感じにしていたが、このelm-spa-exmapleはしっかり外部から叩かれるあるものだけをexposingしてた。

importするときに(..)としてしまうと名前がぶつかったりいろいろと弊害が考えられるけど、正直moduleの宣言は全部exposingするでも問題ない気がするんだがこれは何か理由があるのだろうか…。

MainのModel定義

めっちゃ短くてびっくりした。

こんな感じ。

type alias Model =
    { session : Session
    , pageState : PageState
    }

MainのModelが全てのstateを管理するようなイメージを持っていたので、驚いた。

Sessionには全ページで必要となる情報が入っていて、このアプリだとSessionの定義は

type alias Session =
    { user : Maybe User }

となっていて、ログイン中であればJust UserだしそうでなければNothingが入っているようだった。

で、もう一つのPageStateの方の宣言は

type PageState
    = Loaded Page
    | TransitioningFrom Page

となっている。Pageはその名の通りそれぞれのページで↑で示したようにBlankやNotFoundを除くほぼすべてのページがペイロードに各々のモデルを持っているという構造。

確かにこうすればMainのモデルがずらーーーっと長くなることが防げる。なるほど。

ディレクトリ構造について

ディレクトリ構造についてはここに作者本人が書いてた。(というかその他諸々いろいろ書いているのですごく参考になる気がする)

dev.to

英語が辛いので拙訳ではあるが、まとめておく。もちろん本家を見た方が圧倒的に良い。

Page.* module

アプリケーションの個々のページのロジックを持つ。

具体的にはViewだったりModelだったりUpdate。

加えてTaskというのを使って、データの取得が終わるまで遷移を止めるような処理もやっているっぽい。ここに関してはTaskがわかってないので要勉強感。

View.* module

再利用可能なViewのmoduleたち。

具体的には、UserやFeedが挙げられる。先で言っていたコンポーネントがこれにあたるのではないかと思う。

Data.* module

「共通のデータ構造」らしい。要はリソースなんじゃないかと解釈した。

リソースの構造と、そのリソースのデコーダー・エンコーダーなんかをひとまとめにしておく。

これは記事とは関係ないけど、多分ここがアプリケーションの品質を決める要なんじゃないかなぁと思った。というのも、ちゃんとnullが入る可能性のある属性はMaybeを使って宣言するとかしないと結局実行時エラーになってしまってElm使ってる意味isって感じになってしまうのでここだけはしっかり考えて(存在するなら)APIドキュメント見ながら書くべしなんだと思う。きっと。

Request.* module

HTTPリクエストをまとめたmodule。

先程のDataモジュールに対応する形でmoduleがあって、中ではそのリソースに対して行える操作が定義されている。

URLは各モジュールに書かないで、Request.Helperというmodule内に宣言してある。

Route module

ブラウザのURLをPageに変換する処理を行うmodule。

ブラウザのアドレスバーが書き換わったタイミングで呼ばれるのもこのmodule。

Reqeust同様、生のURLではなくRouteを外部にはexposeする。

Port module

Portについてを扱うmoduelなんだけど私自身portに対する理解が全然足りないので省略。

PortのJS側の処理はこのアプリケーションの場合、index.htmlの中にある。

Main module

言わずとしれたMain

アセットの扱いに関しては何やら議論があるらしい。記事にも書いていたし、ソースコードにもご丁寧にコメントが書いてある。

今後のElmのリリースではいい感じにするらしい。

Util module

名前の通り、Util関数がたくさん詰まったmodule。

演算子まで宣言してあった。すごい。


とりあえず軽くコード読みながらまとめてみた。

実際の挙動は

Conduit

これを見てみるとわかりやすい。(タイムトラベルデバッガーもオンになっている親切仕様)

コードは読んだ。あとは書くだけ……。

どこから手をつけていくのがいいのかがわからんけど、やっぱりモデルのところなのかなー。

雑談

ここ数日花粉がすごいです。割と限界。(薬がここまで効かなかったのは今年が初めてかもしれない)