gaaamiiのブログ

間違ったことを書いている時があります。コメントやTwitter、ブコメなどでご指摘ください

Html.mapでモジュール間のMsgの受け渡しをする

趣味でElm製Markdownエディタを開発しているのですが、ようやくふつうにファイルを開いてふつうに保存することができる感じになってきました。楽しいです。今日はmacOSのナビゲーションを真似して、画面上部にナビゲーションを実装してみました。

今回はこの実装のときに出てきた悩みと解決法について書きます。

モジュールに切り出すときに出てきた悩み

Main.elmはだいぶ大きくなってしまっていたので、モジュールを分けて、こんな感じにしようと思って実装をはじめました。

-- Main.elm
view =
    -- いろいろ省略
    nav [] [
      -- プルダウンのデータを配列で渡したらいい感じに表示されてほしい
      PullDown.view [ filePullDown, viewPullDown ]
    ]

で、なんやかんやしてとりあえず表示するだけのところまではできたのですが、ある悩みが出てきました。

「あれ、Msgってどうやってやり取りすればいいんだろう」

やりたいのは、PullDownモジュールのなかで起きたことを、Mainモジュールのほうでさばくことです。さっきのGIFを見るとわかりやすいのですが、

ナビゲーション内の要素をクリックすると、画面の見た目が変わっています。つまり、「PullDownのView/Theme/Whiteをクリックした」というMsgをどうにかしてMainモジュールに伝えて、アプリケーションの状態を更新する必要があります。

Elm in Actionという本を以前買ってほとんど読まずに放置していたなと思い出し、中身を見たところ、答えが書いてありました。

Html.mapの型はこうなっています。

map : (a -> msg) -> Html a -> Html msg

Html.mapを使うと、Html aHtml bに変換することができます。今回、PullDown.viewの型はHtml PullDown.Msgですから、それをHtml Main.Msgに変換するということです。上記の関数の型にあてはめるとこういうことです。

map : (PullDow.Msg -> Main.Msg) -> Html PullDown.Msg -> Html Main.Msg

なので、このように利用します。

-- Main.elm
type Msg
--  = ... 省略
    -- Main.Msgに、PullDow.Msg -> Main.Msg となるバリアントを用意
    | GotPullDownMsg PullDown.Msg

Html.map GotPullDownMsg (PullDonw.view ...)

これでできました。なるほど1

雑感

ReactとReduxを使った場合はコールバックを渡してこのようになるかと思います。

const Main = (props) => {
  const handleClickPullDown = () => {
    props.dispatchPullDownClickAction();
  }

  return (
    <PullDown
      pullDowns={[ filePullDown, viewPullDown ]}
      onClick={handleClickPullDown}
    >
  )
}

connect(mapStateToProps, mapDispatchToProps)(Main)

Elmの場合はonClickにMsgを指定するのでupdate関数でそのMsgの処理を行うことになりますが、Reactの場合はコールバックを指定するため、そこでいろいろな処理を挟むことも可能です。どこで何をするという処理の流れがElmのほうが厳密で、React/Reduxのほうが自由な印象を受けました。

また、いま自分がつくってるものが小さいアプリケーションなので考える必要がないだけで、もっと大きめのものをつくるとなったら、Modelやupdateの分割方針も考えないといけないのかなという感じがあります。Elm in Actionにはそのようなことが書いてあるのでありがたいです。今後も参考にしていきたいと思います。


  1. だいぶ端折ってしまったので、気になる方はNekobitoの実装を覗いてみてください。