Reactの考え方
これは、元はReactの公式ブログへ投稿されたものです。
個人的な見解になりますが、ReactはJavaScriptを使用した大規模で高速なWebアプリケーションを開発する、 最も優れた方法であると考えています。 これは、FacebookとInstagramにおいて、我々にとって良い結果をもたらしてくれています。
Reactの優れた点の1つに、アプリケーションの構築を、どのように考えさせるかという事が挙げられます。 このページでは、Reactを使用した検索可能な商品データのテーブルを構築する過程を通して、学んでいきます。
- まずは、モック作りから
- ステップ1: UIをコンポーネント階層に分割
- ステップ2: Reactの静的版の構築
- ステップ3: UIステートの必要最小限構成
- ステップ4: ステートを使用するべき場所の特定
- ステップ5: 別(逆)データフローの追加
- 最後に
まずは、モック作りから
JSON APIとデザイナーから受け取ったモックが既にあると仮定して下さい。 このモックを見る限り、我々のデザイナーはどうもイケてないようです…。
JSON APIは次のようなデータを返します。
[
{category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
{category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
{category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
{category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
{category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
{category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
];
ステップ1: UIをコンポーネント階層に分割
まず始めに取り掛かりたいことは、モック内の各コンポーネント(とサブ・コンポーネント)の周りを色付けして、 それら全てに名前を付けることでしょう。 もし、デザイナーと作業しており、彼らが既にそれをしてくれているのであれば、 彼らに尋ねてみて下さい。 もしかしたら、Photoshopレイヤーの名前が、最終的なReactコンポーネントの名前になっているかもしれません。
ただ、あなたはどのようにして、何をコンポーネントにするべきなのかを決めていますか? これには、新しい関数またはオブジェクトを作って決める時と同じテクニックを使用します。 これらのテクニックの1つに、単一責任の原則があり、 理想を言えば1つのコンポーネントは1つだけの役割を持つべきです。 もし、それが肥大化するようであれば、より小さいサブ・コンポーネントに分解するべきです。
JSONデータのモデルをユーザーに対して表示することはよくある事なので、 UI(=コンポーネント構造)上に正しくモデルが構築されていれば、すぐに分かるでしょう。 ユーザー・インターフェースとデータ・モデルは、同じ情報構造になるように作りこまれる傾向があり、 UIを分解してコンポーネントに落としこむ事は、ありふれた作業であることを意味します。 データ・モデルを各1パーツを表すコンポーネントに分割してみましょう。
このアプリケーションが、5つのコンポーネントになった事が確認出来ます。
- FilterableProductTable(オレンジ):このサンプル全体です。
- SearchBar(青):ユーザー入力を受け付ける入力欄です。
- ProductTable(緑):ユーザーの入力を基にデータのフィルタリングと表示を行います。
- ProductCategoryRow(水色):各カテゴリの見出しを表示します。
- ProductRow(赤):各商品の行を表示します。
ProductTable
を見て、("Name"と"Price"ラベルを含む)テーブルヘッダーが、
それ自身のコンポーネントの所有になっていないことに気づくでしょう。
これは好みの問題であり、意見が別れるところでしょう。
この例では、ProductTable
の責任範囲となる描画データ集合の一部であることから、
ProductTable
の1つのパーツとしています。
ただし、もしこのヘッダーが今後、複雑になるようであれば(例えば、ソート機能の追加等)、
ProductTableHeader
コンポーネントを作るべきでしょう。
モックの各コンポーネントを明確にしたので、 次は階層構造を決めていきましょう。 この作業はとても簡単です。 モックで表される、あるコンポーネント内の別コンポーネントは、階層上での子であるべきだからです。
FilterableProductTable
SearchBar
ProductTable
ProductCategoryRow
ProductRow
ステップ2: Reactの静的版の構築
コンポーネントの階層が決まったところで、アプリケーションの実装に取り掛かります。 最も簡単な構築方法は、データ・モデルを受け取り、インタラクティブ機能を省いてUIを描画することです。 静的なバージョンを構築する場合は、多くのタイピングを必要とする反面考える事は少なく、 インタラクティブな機能を追加する場合は、考える事が多い反面、タイピングは少なくなります。 何故なのか考えてみましょう。
データ・モデルを描画するアプリケーションの静的なバージョンを構築するために、 他のコンポーネントを再利用し、propsを使用してデータを渡すコンポーネントを構築したいと考えるでしょう。 propsは親から子へデータを渡すための手段です。 もし、ステート(state)を使い慣れているとしても、 この静的なバージョンを構築するのにステートは一切使用しないで下さい。 ステートは相互データの受け渡し専用に予約されているものであり、 ここではあくまで静的版を扱っており、データ変更は必要としていません。
トップ・ダウンまたはボトム・アップのどちらからでも構築する事が出来ます。
つまり、コンポーネントの階層構造の上(FilterableProductTable
)からでも、
下(ProductRow
)からでも、作り始めることが出来るということです。
通常はシンプルな例であればトップ・ダウンが作り易く、
大規模なプロジェクトであればボトム・アップが作り易く、
テストも構築時と同じように書きます。
このステップの最後で、データ・モデルを描画する再利用可能なコンポーネントのライブラリを持つことになります。
ここでは静的バージョンのアプリケーションになるため、そのコンポーネントはrender()
メソッドを持つだけになります。
階層トップのコンポーネント(FilterableProductTable
)は、prop
としてデータモデルを取得します。
もし、基盤となるデータ・モデルに変更を加え、再度React.render()
を呼び出すと、
そのUIは更新されます。
Reactの一方向データフロー(または、一方向バインディングとも呼ばれます)は、
全てをモジュールとして推論しやすく速いものとして維持するため、難しいことは何もなく、
そのため、UIがどのように更新され何処が変更されたのかを容易に把握する事が出来ます。
props と state
Reactの"モデル"データには、porps
とstate
(ステート)の2つのタイプが存在します。
この2つを理解する事と使い分ける事は非常に重要であるため、
もしこれらの違いを正確に把握していないのであれば、こちらのドキュメントを参照して下さい。
ステップ3: UIステートの必要最小限構成
相互にデータを受け渡す(インタラクティブな)UIを作成するには、基盤となるデータ・モデルを変更するトリガーが必要となります。 Reactではstateを使用して、これを容易に実装することが出来ます。
アプリケーションを構築するために、まず必要とされる変更可能なステート(state)の最小セットを考える必要があります。
ここでは、DRY(Don't Repeat Yourserlf)が重要となります。
アプリケーションが必要とするstateの絶対的な最小構成を考え、それ以外は状況に応じて算出するようにします。
例えば、TODOリストを構築する場合はTODOの項目配列だけを保持し、
項目数(カウント)をstateの変数に分離して保持するという事はするべきではありません。
TODOの項目数(カウント)を表示したい場合は、代わりに項目配列のlength
を使用すればよいでしょう。
この例のアプリケーションで扱う全てのデータを明確にしていきましょう。
- 元の商品リスト
- ユーザーが入力する検索テキスト
- チェックボックスの値
- 商品のフィルターリスト
1つ1つ確認し、そのstateについて考えてみましょう。 各データについて、シンプルに3つの問いかけをしてみます。
- それはpropsを通して親から渡されていますか? もしそうであれば、それはstateではありません。
- それは、何度も変更されますか? もしそうで無ければ、それはおそらくstateではありません。
- それは、コンポーネント内で他のstateまたはpropsの基に計算出来るものですか? もしそうであれば、それはstateではありません。
商品の元のリストはpropsとして渡されるため、これはstateではありません。 検索テキストとチェックボックスは、何度も変更されることと、他の要因から算出することが出来ないことから、 stateであると考えられます。 最後に、商品のフィルタリングされたリストは元の商品リスト、検索テキスト、チェックボックスの値を用いて算出可能であるため、 stateではありません。
最終的にstateは次のようになります。
- ユーザーに入力された検索テキスト
- チェックボックスの値
ステップ4: ステートを使用するべき場所の特定
OK、これでアプリケーションのstateの最小構成を明確にすることが出来ました。 次はコンポーネントが可変であるか、stateを所有するのかを明確にしていきましょう。
注意: Reactでは、データが全てコンポーネント階層の上から下に向かって一方向に流れるデータフローになります。 コンポーネントが何のstateを所有するべきかは、すぐに明確に出来ないかもしれません。 新しくReactを始めて理解しようとする人にとって、ここが最も障壁となるパートになりがちです。 そのため、以降のこれらのステップをしっかりと理解していきましょう。
アプリケーションの各stateは次のようになります。
- stateを基に何かしらを描画する各コンポーネントを明確にします。
- 共通のオーナーとなるコンポーネントを見つけます。 (stateを必要とする全てのコンポーネントの最上層にいる単一のコンポーネント)
- 共通のオーナー、もしくは階層の異なる別のコンポーネントのどちらかがstateを所有するべきです。
- もし、あるstateを所有させるのに適したコンポーネントが見つからない場合は、 stateを保持するだけの新しいコンポーネントを作成し、 共通のオーナーコンポーネントより上の階層の何処かに追加して下さい。
アプリケーションを作成するにあたり、この方針に沿って進めてみましょう。
-
ProductTable
はstateを基に商品リストをフィルタリングする必要があり、SearchBar
は検索テキストを表示し、stateを確認する必要があります。 -
共通のコンポーネントのオーナーは、
FilterableProductTable
になります。 -
FilterableProductTable
内に、フィルター・テキストとチェック値があるのは概念的に理に叶っていると言えます。
以上のことから、FilterableProductTable
内にstateを置くことにしました。
まず、{filterText: '', inStockOnly: false}
を返してアプリケーションの初期stateを反映する、
getInitialState()
メソッドをFilterableProductTable
へ追加します。
次にfilterText
とinStockOnly
をpropとして、
ProductTable
とSearchBar
に渡します。
最後に、これらのpropsを使用してProductTable
内の行をフィルタリングし、
SearchBar
内のフォーム・フィールドからの値を設定します。
これで、アプリケーションがどのように振る舞うのかを確認することが出来るようになりました。
filterText
に"ball"と入力してみましょう。
データ・テーブルが正しく更新されることが確認出来るでしょう。
ステップ5: 別(逆)データフローの追加
ここまでで、stateが階層を下っていき、propsの関数として正しく描画されるアプリケーションを構築してきました。
今度は、別のデータ・フローをサポートしてみましょう。
階層の深い場所にあるフォーム・コンポーネントは、FilterableProductTable
内のstateの更新を必要とします。
Reactはこのデータ・フローを明確にし、どのようにデータが流れるのかを分かり易くしてくれますが、
典型的な双方向データ・バインディングと比較すると、少し多めのタイピングが必要となります。
Reactは双方向バインディングと同じようような便利なパターンにするReactLink
と呼ばれるアドオンを提供しますが、
この記事の目的を失わないために、全てを明確にしておきます。
もし、この例の現在のバージョンでキーボード入力またはチェックボックシェのチェックをしようとしても、
Reactが入力を無視することが確認出来ます。
これは、FilterableProductTable
から渡されるstate
と、
input
のvalue
プロパティが常に等価になるように設定するために、
意図的にこうしています。
ここで、どのように動作させたいのかを考えてみましょう。
ユーザーがフォームの状態を変更する度に、stateを更新してユーザー入力にそれが反映されることを確認したいと思います。
コンポーネントは自分自身のstateだけを更新するべきであるため、
FilterableProductTable
は、stateの更新が必要となる度に実行されるコールバックをSearchBar
へ渡します。
入力欄のイベントの検知には、onChange
を使用することが出来ます。
また、FilterableProductTable
によって渡されるコールバックは、
setState()
で呼び出し、アプリケーションを更新します。
やるべき事が数多くあるように感じますが、実際には僅か数行の内容になります。 そして、アプリケーション内をデータがどのように流れているのかが、本当に明確になります。
最後に
この解説を通して、Reactを使用したコンポーネントとアプリケーションの構築についての考え方を伝えることが出来たのなら幸いです。
あなたが慣れ親しんだ方法よりも、多少コード量が多くなるかもしれませんが、 コードは書くよりも読むことのほうがはるかに多い事と、 このモジュール方式とコードの明確化が極めて読み易いものであるという事を忘れないで下さい。
大規模なコンポーネントのライブラリの構築を始める際には、 この明確さとモジュール性、そしてコードの再利用による作業の圧縮によって、 多くの恩恵を受けることが出来るでしょう。