Reason-React
https://reasonml.github.io/reason-react/
ReasonMLでReactを利用することができるのですが、通常のReactの感覚でFormを扱おうとしたら、いくらかハマったので、それをまとめました。
Formを操作するサンプル
完成形はこちらです。
Beltを使っているので、こちらの記事も参考にしてください。
https://qiita.com/soebosi/items/404e035b74636ee8b406
open Belt;
type state = {
name: string,
nameArray: array(string),
};
type action =
| AddName(string)
| ChangeText(string);
let s_ = ReasonReact.stringToElement;
let a_ = ReasonReact.arrayToElement;
let component = ReasonReact.reducerComponent("TopPage");
let make = (_children) => {
let handleChange = (e, self) => {
let target = ReactEventRe.Form.target(e);
let name = ReactDOMRe.domElementToObj(target)##value;
self.ReasonReact.send(ChangeText(name));
};
let handleSubmit = (e, self) => {
ReactEventRe.Form.preventDefault(e);
self.ReasonReact.send(AddName(self.state.name));
self.ReasonReact.send(ChangeText(""));
};
{
...component,
initialState: () => {
{
name: "",
nameArray: [||],
};
},
reducer: (action, state) => {
switch(action) {
| ChangeText(name) => ReasonReact.Update({...state, name})
| AddName(name) => {
let nameArray = Array.concat(state.nameArray, [|name|])
|. SortArray.stableSortBy(Pervasives.compare);
ReasonReact.Update({...state, nameArray});
}
}
},
render: self =>
<div>
<form onSubmit={self.handle(handleSubmit)}>
<label>
{s_("Name:")}
<input _type="text" name="name" onChange={self.handle(handleChange)} value={self.state.name} />
</label>
<input _type="submit" />
</form>
<ul>
{a_(
Array.mapU(self.state.nameArray, (. name) =>
<li key={name}>{s_(name)}</li>
)
)}
</ul>
</div>,
};
};
動作はシンプルで、input要素に名前を入力して、submitボタンを押すとその名前をリストに追加します。
そして、名前のリストをli要素として出力するというものです。
以下がこのサンプルアプリを作成した際にハマったことになります。
input type
typeはReasonMLでは予約語なので、以下のように使用することができません。
<input type="text" name="name" />
公式のドキュメントに対処方法が記載されています。
https://reasonml.github.io/reason-react/docs/en/invalid-prop-name.html#docsNav
_type
とアンダースコアを前につけることで回避しています。
<input _type="text" name="name" />
余談ですが、valも予約語で、これを変数名に使用すると、以下のコンパイルエラーになります。
Error: 1576: val is a reserved keyword, it cannot be used as an identifier. Try `val_' instead
こちらは、アンダースコアを後ろにつけるので、混乱しそうです。
(アンダースコアを変数名の先頭につけると、使用しない変数という意味になるので、当たり前といえばそうなのですが。。)
event.target.value
JavaScriptでは、inputタグに入力された値をonChangeで受け取って利用することがよくあります。
値の取得方法は、引数で受け取ったeventのevent.target.value
なのですが、ReasonMLの場合は、これでは取得できません。
こちらも公式に利用方法が書いてあります。
https://reasonml.github.io/reason-react/docs/en/event.html#docsNav
公式は、ワンラインになっていますが、私は以下のようにして利用しました。
let handleChange = (e, self) => {
let target = ReactEventRe.Form.target(e);
let name = ReactDOMRe.domElementToObj(target)##value;
self.ReasonReact.send(ChangeText(name));
};
出力結果を見ると、まさにevent.target.valueに変換されていました。
var handleChange = function (e, self) {
var target = e.target;
var name = target.value;
return Curry._1(self[/* send */4], /* ChangeText */Block.__(1, [name]));
};
引数のselfについて
reason-reactでは、stateを管理するためにselfというオブジェクトがあります。
通常は、render関数の引数として渡ってくるのですが、
<input _type="text" onChange={self.handle(handleChange)} />
のようにself.handleを利用することで、render関数外に定義された関数でもselfを触ることができます。
event.preventDefault
formのsubmitはデフォルトではページ遷移をしてしまうので、それをキャンセルしたいことがよくあります。
JavaScriptでは、event.preventDefaultを使用するのですが、これまたReasonmlの場合、別の手段になります。
こちらは、公式のドキュメントで見つからずソースコードのインラインドキュメントから対応方法がわかりました。
https://github.com/reasonml/reason-react/blob/380358e5894d4223e7dd9c1fb2df72f0756231bc/src/reactEventRe.rei#L1
let handleSubmit = (e, self) => {
ReactEventRe.Form.preventDefault(e);
/* "省略" */
}
今回は、formの挙動をキャンセルしたいので、ReactEventRe.Form.preventDefault
を使用しています。
マウスのイベントをキャンセルしたい場合は、ReactEventRe.Mouse.preventDefault
のように、各イベントごとに違うモジュールを利用するので注意です。
まとめ
ReasonMLは、JavaScriptのシンタックスに近いとはいえ、やはりハマりどころはある印象です。
とくに型まわりで考えることが多いと思っています。
逆に型の変換さえうまく行けば、Reactの知識がそのまま活用できます!
型自体は、すばらしいものなので、世の情報量が増えること、コンパイルエラーがわかりやすくなることが大切かなと思いました。
少しでも、ReasonMLを使う際に役立つ情報となれば幸いです。