reasonml

ReasonReact を使ってみる

Reason 気になった人はまず最初で使うであろう ReasonReact について解説する。

https://github.com/reasonml/reason-react

外部パッケージの使い方

npm経由で落として、bsconfig.json にバインディングがあるので、それを教えてやる。

yarn add reason-react
bsconfig.json
{
  ...
  "bs-dependencies": ["reason-react"]
}

これで bucklescript が ReasonReact 名前空間にアクセスできる。

Hello, ReasonReact

コンポーネントを定義して render する。

main.re
module App {
  let make = (_children) => {
    ...ReasonReact.statelessComponent("App"),
    render: (self) => <div> { ReasonReact.stringToElement("Hello") } </div>
  };
}

/* mount */
ReactDOMRe.renderToElementWithId(<App />, "root");

このコードを見る限り、ReasonReact は make という関数を見つけて React.createElement 相当の展開をする。
文字列は ReasonReact.stringToElement("Hello") という感じで埋める。多少面倒くさい。

名前空間の話

ここで、初歩っぽい話をするが(自分はocaml詳しくないので、こういうタイミングで新しいことを知る)、 ocamlのファイルスコープはファイル名が PascalCase に変換されて module 名になるが、自分自身で module を定義することも出来る。

なので、別ファイルに切り出すならこうなる

app.re
let make = (_children) => {
  ...ReasonReact.statelessComponent("App"),
  render: (self) => <div> { ReasonReact.stringToElement("Hello") } </div>
};

bucklescript 的には外部の名前空間へのアクセスを発見すると require('./src/App') 的な変換をする。

reducerComponent

Reactのアプリを作るなら、 redux 相当のことをしたいよな、と思って、本当はここで reductive という redux クローンの解説をしようと思っていたが、 ドキュメントいわく、 You might not need this library, なぜなら ReasonReact が reducer 相当の機能を持ってるので、まずはそれを使え、という話。

https://github.com/reasonml-community/reductive は後日解説する。(こうやって記事を稼ぐ)

https://reasonml.github.io/reason-react/blog/2017/09/01/reducers.html を読みながらカウンターを実装してみた。

type action =
  | Increment
  | Decrement;

let counter = (action, state) =>
  switch action {
  | Increment => state + 1
  | Decrement => state - 1
  };

module App = {
  let make = (~value, ~onClickIncrement, ~onClickDecrement, _children) => {
    ...ReasonReact.statelessComponent("App"),
    render: (self) =>
      <div>
        <span> (ReasonReact.stringToElement(string_of_int(value))) </span>
        <button onClick=onClickIncrement>
          (ReasonReact.stringToElement("+1"))
        </button>
        <button onClick=onClickIncrement>
          (ReasonReact.stringToElement("-1"))
        </button>
      </div>
  };
};

let make = (_children) => {
  ...ReasonReact.reducerComponent("AppContainer"),
  initialState: () => 0,
  reducer: (action, state) => ReasonReact.Update(counter(action, state)),
  render: (self) =>
    <div>
      <App
        value=self.state
        onClickIncrement=(self.reduce((_) => Increment))
        onClickDecrement=(self.reduce((_) => Decrement))
      />
    </div>
};

counter が ピュアな reducer 定義、 App が View を持つComponent、 トップレベルの make がいわゆる ContainerComponent という感じの分担。ちょっとややこしいのが self.reduce が高階関数になっている。

JS でやるときの無理矢理な action 定義と違って、言語組み込みの機能で自然に表現できている。

次の記事の候補

  • reductive さっと調べる限り connect 実装してないが、それはどうする?
  • Reason から JS で定義した React Component を呼ぶ
  • JS から Reason で定義した React Component を呼ぶ

まだ調べてない。わかったら書く。