elm-spa-exampleのコードを読んでみた所感メモ
ElmでSPA作ってるならこれいいよと教えてもらったサンプルコード。
これを読んでみて、初心者なりに感じたことをメモする。
このメモは正確でない可能性があるので信用しないでください。また間違ったことを言っていたら教えていただけると嬉しいです。
あと順不同です。気がついた順で書いてる。
ファイル分けについて
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のモデルがずらーーーっと長くなることが防げる。なるほど。
ディレクトリ構造について
ディレクトリ構造についてはここに作者本人が書いてた。(というかその他諸々いろいろ書いているのですごく参考になる気がする)
英語が辛いので拙訳ではあるが、まとめておく。もちろん本家を見た方が圧倒的に良い。
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。
演算子まで宣言してあった。すごい。
とりあえず軽くコード読みながらまとめてみた。
実際の挙動は
これを見てみるとわかりやすい。(タイムトラベルデバッガーもオンになっている親切仕様)
コードは読んだ。あとは書くだけ……。
どこから手をつけていくのがいいのかがわからんけど、やっぱりモデルのところなのかなー。
雑談
ここ数日花粉がすごいです。割と限界。(薬がここまで効かなかったのは今年が初めてかもしれない)