いきなり前回のあらすじ
最近は、業務外でTypeScript + Reactを勝手に勉強している。
— ㅤ (@tercel_s) 2016年7月14日
直近で仕事で活かす機会はないけれど、これ、勉強していると本当にたのしい(´ω`*)
昔、Processingをやっていた頃のような、純粋な技術の楽しみを新たに発見したような気持ち。
というわけで、残念ながらお仕事で使う機会は今のところないにも関わらず React に手を出してしまいました。
前回の更新から1ヶ月以上も空いてしまったけれど気にしない。
ちなみに本日の内容も相変わらず低レベルです。 Flux とか Redux とかに到達する以前の問題です。 仕事でバリバリ React を使っているようなプロフェッショナルにとっては何一つ有益な情報が書かれていないので回れ右!
React 勉強記
おそらく日本語で読める唯一の入門書『入門 React ―コンポーネントベースのWebフロントエンド開発』を買いました。しかもだいぶ前に。
React は触っていてかなり楽しいけれど、独自のルールのおかげで戸惑うことがしばしばあります。
まず前提として、React には以下の掟があります。
これは今時点での僕の理解なので、もしも間違っていたらごめんなさい。
子コンポーネントで発生したイベントを親側はどうやって検知すればいいの問題
React は「データの流れが親から子へ一方通行」という制約のおかげで、イベントまわりの書き方に一癖あります。
たとえば、あるコンポーネントのイベントのせいで、他のコンポーネントに影響が及ぶシチュエーションを考えてみましょう*1。
常識的に考えて、必ずしもイベントの発生元が常に親コンポーネントであるとは限りません。もしも子から親にイベントの発生を通知する必要がある場合は、ちょっとした技巧が必要となります。
『入門 React ―コンポーネントベースのWebフロントエンド開発』の §6.2〜6.3 から、当該問題についての記述を引用します:
注意深い読者の方はすでにお気づきかもしれませんが、このコンポーネントは実は重要な要素を欠いています。それは何かと言うと、値が変更されたことを親コンポーネントに伝える機能が実装されていないのです。
(中略)
子コンポーネントが親コンポーネントと通信する最も簡単な方法は props を使用することです。親がコールバック関数を props として渡して、子が適宜それを呼び出すといった形です。
が、個人的にこれは悪手だと思っています。
このやり方は、コンポーネントの階層が深くなると、間にいるコンポーネントたちは自分自身となんの関係もないコールバック関数をひたすら中継するようになるからです。イメージとしてはこんな感じ。
Reactをそのまま使うと、コンポーネントの階層構造が深くなったときに問題が生じる。
— ㅤ (@tercel_s) 2016年7月16日
上位のコンポーネントから下位のコンポーネントにStateを引き渡す状況では、間にいるコンポーネントが自分にとって別にどうでもいいようなデータもいちいち中継しなくてはならない。
そんなこんなでもやもやしていたところ、 Qiita の「Reactを使うとなぜjQueryが要らなくなるのか」という記事を見つけました。
実はこの記事、僕が React に手を出す前(5月末頃)に一度目を通していたのですが、当時はいまひとつピンとこないままでした。
Reactを使うとなぜjQueryが要らなくなるのか https://t.co/zvbyXYMsKv
— ㅤ (@tercel_s) 2016年5月26日
アニメアニメしたいときは、まだjQueryを使うことが多いかも……(´・ω・`)
ところが今になって読み返すとかなりの良記事。結論から言うと、子コンポーネントのイベント云々問題の解もここに書いてありましたし、ついでに前回僕が載せたコーディングスタイルはもはや時代遅れであることも発覚しました。
まぁそんなわけで Hello, World からやり直しです。
いまふたたびの Hello, World
前回の書き方
今回の書き方
変わったところ
前回はReact.createClass()
を用いてコンポーネントを作成していましたが(下図左)、多くの場合、アロー関数式で簡潔に書き換え可能だそうです(下図右)。ちょっと構文が新しすぎて JSFiddle のシンタックスハイライトがうまく作用してくれませんでした。
で、ここがポイントなのですが、構文をアロー関数式に切り替えたことで、コンポーネントはstateを持てなくなりました。
状態を表す変数はコンポーネント内部に持たせるのではなく、ひとところに集めて一元的に集中管理するのが今時の正義らしいです。
あともうひとつのポイントが、再描画用の ReactDOM.render()
をいつでも何度でも呼べるよう、render
という定数にしておきます。
メタボ化した props をすっきりさせる
ここでいよいよ本題に移りたいと思います。
前置きが長すぎてすっかりメインテーマを忘れかけていますが、「複数階層に渡るコンポーネント間でイベントをいい感じに扱う話」なのでした。
要件としては以下2点です。
- イベントの影響を受けるすべてのコンポーネントが矛盾なく書き換わってほしい
- そのためのコードはできるだけ簡潔に書きたい
そこで、とりあえずの題材として、テキストボックスを変更すると、挨拶文が連動するという実につまらない前回のサンプルを改めて引っ張り出してきました。動き自体は果てしなくショボいのですが、以下の特徴を兼ね備えているのでよしとします。
- 階層間のデータのやり取りが発生する
- イベント発生時に、直接の親子関係にないコンポーネントが影響を受ける
実際に動くサンプルがこちら。
前回のサンプル(実はすでにリファクタリング済み版)
これ、もともとは子コンポーネントの状態変化を親が監視できるよう、親コンポーネントが自身のコールバック関数を propsとして子コンポーネントに引き渡していました。
ところが今回、Qiita のソースを パクって 参考にして
- シングルトンな状態変数を導入
- 状態変数が変化したら再描画関数を実行
という新たな思想を導入することにより、親コンポーネントが子の状態変化を監視する必要そのものがなくなりました。
プログラムの内部動作も下図のように変わっています。
本当はリファクタリングの内容をステップバイステップで書こうかと思いましたがいい加減面倒になったのでやめました。
ちなみにさっきのサンプルのソースは既に今回の内容に合わせてアップデートしてあります。props からわずらわしいコールバック関数が消え去っているのがチャームポイント。「あぁ、そういうことか」と理解できた瞬間はうれしい。
本日のまとめ
Qiita にだいたいのことが書いてあるのでそっちを見ればよいと思う。なげやり。
あと周りに React やっている友達がいなくてさみしい。
*1:具体的には、「ボタンが押されたらテキストエリアの入力値をチェックして、内容に不備がある場合はエラーメッセージを表示する」といった仕掛けなどを思い浮かべてください。