この記事について
正月にElmを使って自分用のブログのクライアントサイドを作っていたのですが、つまずいたことを備忘録として書いておきます。
なお、公式ガイド (日本語版はこちら)をサーッと目を通してから触り始めてつまずいたことを書いているので、とりあえずそちらを読んだ方を想定しています。
ちなみに、私はReactとかはちょこっと触っていてなにか作ったことはあるものの、Reduxとか使ってまじめな開発はやっていないという感じのレベルです。
開発環境をどう作るべきか?
@ababup1192さんのこちらの記事をほぼ丸パクリさせてもらいました。
https://gist.github.com/ababup1192/a1a091bcc0e535d180544639f531302c
最初elm reactorとか使って開発していたのですが、いざJSにコンパイルして、html内に読み込ませて、ほんでcssも読み込ませて―とか考えだすとやっぱりwebpackとか使ったほうがよいですね。
ちなみに、@ababup1192さん曰く、開発環境のベースリポジトリとしては最近はこちらの方がおすすめとのことでした(情報提供ありがとうございます!)。
https://github.com/simonh1000/elm-webpack-starter
https://t.co/lv0uZBTylH
— ABAB↑↓BA (@ababupdownba) 2019年1月5日
最近はこっちに乗り換えてしまいました・・・。
elm-dev-env だと、コンパイルがなぜか激重なので、こちらに乗り換えてください!使い勝手ほぼ一緒です!
Navigationでページ遷移を実装
公式ガイドのナビゲーションするを見ながら、ページ遷移を作って行きました。
が、公式ガイドの例にはURLによってViewを切り替える例がなかったため、小一時間どうやるべきなのか考えてしまいました。
とりあえず公式ガイドを読みなおし、URLをパースするの統合の章に書いてある内容を参考に実装していきました。
あと、elm-spa-exampleのソースコードも参考にさせてもらいました。
どうやら、UrlからRoute型のvariantsを作って、Modelにもたせてやればよさそうな感じがしたので、こんな感じの実装になりました。
-- URL PARSER
type Route
= TopPage
| NextPage String
routeParser : Parser (Route -> a) a
routeParser = oneOf [ map NextPage (s "next" </> string) ]
parseUrl : Url.Url -> Route
parseUrl url =
let
parsed =
-- #をパースするためにUrlをparseする前にfragmentの設定してる
-- https://package.elm-lang.org/packages/elm/url/1.0.0/Url
-- 以下の実装を参考にした
-- https://github.com/rtfeldman/elm-spa-example/blob/b5064c6ef0fde3395a7299f238acf68f93e71d03/src/Route.elm#L59
{ url | path = Maybe.withDefault "" url.fragment, fragment = Nothing }
|> parse routeParser
in
case parsed of
Just route -> route
Nothing -> TopPage
-- UPDATE
type Msg
= LinkClicked Browser.UrlRequest
| UrlChanged Url.Url
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
LinkClicked urlRequest ->
case urlRequest of
Browser.Internal url ->
( model, Nav.pushUrl model.key (Url.toString url) )
Browser.External href ->
( model, Nav.load href )
UrlChanged url ->
let
-- Urlが変更されたら、parseUrlでRoute型variantsへ変換
route = parseUrl url
in
( { model | url = url, route = route }, Cmd.none )
-- VIEW
view : Model -> Browser.Document Msg
view model =
case model.route of
TopPage ->
{ title = "URL Interceptor"
, body =
let
contentPath = "/#/next/"
in
[ text "The current URL is: "
, b [] [ text (Url.toString model.url) ]
, ul []
[ viewLink <| contentPath ++ "foo"
, viewLink <| contentPath ++ "bar"
]
]
}
NextPage param ->
{ title = "URL Interceptor"
, body = [ text param ]
}
コードの全体はGistに貼っておきます。
https://gist.github.com/yuizho/c0b08ee59d3da81891426b17209fff4d
トップがListのJsonをDecodeする
トップレベルがリストのJsonをどうDecodeするのかでちょっと悩みましたが、
Decode.listを使えば一発でした。
たとえば、こんなJsonをデコードしたかったら、
[
{
"title": "タイトル",
"id": 1,
},
{
"title": "タイトル2",
"id": 2,
},
]
こんなかんじですね。
type alias Article =
{ title : String
, id : Int
}
articlesDecorder : Decode.Decoder (List Article)
articlesDecorder =
Decode.list
(Decode.map2 Article
(Decode.field "title" Decode.string)
(Decode.field "id" Decode.int)
)
ページを初期化するために複数のAPIをたたきたい
一つのAPIをたたいてその結果を元にModelを更新する方法は公式ガイドに書いてあったのでスムーズに実装できました。
が、複数のAPIをたたき、その複数の結果を同期してModelを更新する方法がよくわかりませんでした。
いろいろ漁っているとどうやら、Taskを使うとJavaScriptのPromise.allみたいな感じで複数の非同期処理の結果を同期して取得できるらしい。
https://www.reddit.com/r/elm/comments/91t937/is_it_possible_to_make_multiple_http_requests_in/e30vlns/
ただ、これをHttp.getとかに適用する方法がよくわからない……
そんなときにHttp.toTaskというものがあることを発見しました。
こんな感じでHttp.getの結果を食わしてやれば、前述のredditのサンプルコードと同様にタスクとしてTask.map2へ渡すことが出来た。
Http.get articleUrl articleDecorder |> Http.toTask
全体像としてはこんな感じ。
-- UPDATE
type Msg
= ShowContent (Result Http.Error ( Article, String ))
-- ...
-- HTTP
fetchContent : String -> Cmd Msg
fetchContent id =
let
articleTask =
Http.get articleUrl articleDecorder |> Http.toTask
contentTask =
Http.getString contentUrl |> Http.toTask
in
Task.attempt ShowContent <|
Task.map2 (\article content -> ( article, content )) articleTask contentTask
ちなみに、これ実装するのに参考にしたソースが数ヶ月前にcloneした公式ガイドにのっていたサンプルコードだったため、使ったHttpのバージョンが古いです(1.0.0。いまは2.0.0がでている)。
多分2.0.0でも似たような感じで同様のことができるんだろうと思います。
おわりに
これはElmに限ったことでは無いと思うのですが、公式ガイドに書いてあることは真似して実装できるがそれ以外のことはすべてつまずくみたいな感じでした(やり方がわかってみれば、なーんだ簡単じゃんって感じなんですが)。もっと触りつづけよう。
またなにか増えたら書いていこうと思います。
一週間程度Elmを触ってみて、Elmはコンパイルが通るまではなかなか大変だけど、一度コンパイルが通ってしまえばほぼ考えた通りに動くという感じでなかなか心地よいです。
まだテストコードとか書いてないんですが、そんなに一生懸命テストを書かなくても大丈夫そうだなぁという気持ちです。
しばらく触ってみて、この辺の感想が変わってきたら追記しようと思います。