いろいろあって、他の仮想DOMライブラリからReactも使おうと入門してみたのですが、仮想DOM以外のところの概念に少し感心してしまいました。
入門の経緯
もともと、複雑化するビュー内の制御にjQueryだけで行き詰まっていたところで、Riot.jsを使い始めました。
これはCSSまで1モジュールになっている、呼び出す側にはHTMLでタグを埋め込むだけなど、比較的使いやすかったのですが、もともとの制御がDOMベースとなっていることもあって、動的に<table>
を構築するには限界がある、そしてHTML断片を変数に入れて制御することができないので、1タグの中でしか使い回さない要素などの細かなレベルでの使い回しが難しい、というような問題を抱えていました。
そして、シンプルな仮想DOMライブラリとしてPicodomを導入したのですが、シンプルすぎてモジュール分けがうまく行かなかったこと、そしてライブラリの名前すらpicodom→ultradom→superfineと転々とするように、ライブラリとして安定しない状態が続いたこともあって、使い続けるのに困難さを覚えるようになっていました。
そこで、「一定のユーザー層があって、ライブラリとしてある程度安定していること」も加味したところ、Reactというところにたどり着いた次第です。
フォーム要素の4つの役割
何気なく使っている、<input>
などのフォーム要素には、大きく分けると4つの役割があります(もちろん、設定やフォームの性質上、特定の役割を持たないこともあります)。
- ユーザーからの値の入力
- 設定した状態の保存
- 状態の表示
- フォーム送信時に値を伝える
JavaScriptでフォームを制御する場合、2の「状態の保存」が厄介になる要因の1つで、JavaScriptで持っている値とフォーム側の値が一致しなくなって混沌としたりもします。
ここでReactは、「フォームには、実質的に値を保存させない」(仮に変更しようとしても、何も設定しなければReact内で持っている値から変わらない)という方法で問題を解決しています1。フォーム自体ではない、JavaScriptで管理できる1箇所(single source of truth)で値を管理する構造を取るためのビルディングブロックです。
値の流れ
1階層のコンポーネントの場合
あるコンポーネントの内部にある<input>
で、それを変化させても外部に伝える必要のない場合、Reactでは以下のように構築します。
-
<input>
のvalue
には、state
のプロパティをセットする -
onChange
にセットしたイベントハンドラで、this.setState
を行って、state
内を書き換える
ここでは、値が変化するのはstate
内のたった1つだけ、という形でsingle source of truthを実現しています。
多層のコンポーネントの場合
ここでは、Reactのコンポーネントが持つ2つの属性であるstate
とprops
について考えてみましょう。
-
state
…コンポーネントが内部状態として持つ値、基本的に外部から設定・参照すべきものではない -
props
…コンポーネントに外部から設定する値、コンポーネント内で書き換えてはいけない
となれば、子コンポーネントから親コンポーネントの値を変更したい場合の手段がないようにも見えます。じつは、ここでもネイティブのDOM要素と同様に、props
の一部としてイベントハンドラをセットする方法が推奨されています。「DOM要素でイベントが起きる」→「子コンポーネントでもprops
にセットされたイベントを呼ぶ」→「親コンポーネントが自らのstate
を更新する、あるいは更に上にイベントを伝える」→「それをprops
として子コンポーネントに伝える」→「子コンポーネントからprops
としてDOM要素に値を流す」という形で、親コンポーネントより上にsingle source of truthを集約する形となります。
まとめ
「値を自分で管理するコンポーネント」と「外部から来た値とイベントだけを管理するコンポーネント」をきちんと峻別しないと混沌とする、と感じました。
-
プログラム的に値を書き込めない
<input type="file">
を筆頭に、Reactで値を管理しないで使うという手段も、なくはないです。 ↩