React的な考え方
Reactは、あなたが見るデザインや構築するアプリについての考え方を変えることができます。Reactでユーザーインターフェースを構築するときは、まずそれをコンポーネントと呼ばれる部品に分解します。次に、各コンポーネントの異なる視覚的状態を記述します。最後に、データがコンポーネント間を流れるように、それらを接続します。このチュートリアルでは、Reactで検索可能な商品データテーブルを構築する思考プロセスを案内します。
モックアップから始める
JSON APIとデザイナーからのモックアップが既にあると想像してください。
JSON APIは次のようなデータを返します:
モックアップは次のようになります:

ReactでUIを実装するには、通常、同じ5つのステップを踏みます。
ステップ1: UIをコンポーネント階層に分解する
まず、モックアップ内のすべてのコンポーネントとサブコンポーネントの周りにボックスを描き、名前を付けます。デザイナーと一緒に作業している場合、彼らはすでにデザインツールでこれらのコンポーネントに名前を付けているかもしれません。聞いてみましょう!
背景によって、デザインをコンポーネントに分割する方法は異なります:
- プログラミング—新しい関数やオブジェクトを作成すべきかどうかを判断するための同じテクニックを使用します。そのようなテクニックの一つが関心の分離です。つまり、理想的にはコンポーネントは一つのことだけに関心を持つべきです。もし大きくなりすぎた場合は、より小さなサブコンポーネントに分解されるべきです。
- CSS—クラスセレクターを何に対して作成するかを考えます。(ただし、コンポーネントは少し粒度が粗いです。)
- デザイン—デザインのレイヤーをどのように整理するかを考えます。
JSONが適切に構造化されている場合、それがUIのコンポーネント構造に自然にマッピングされることがよくあります。これは、UIとデータモデルがしばしば同じ情報アーキテクチャ、つまり同じ形状を持っているためです。UIをコンポーネントに分離し、各コンポーネントがデータモデルの一部に一致するようにします。
この画面には5つのコンポーネントがあります:

FilterableProductTable(灰色)はアプリ全体を含みます。SearchBar(青色)はユーザー入力を受け取ります。ProductTable(ラベンダー色)はユーザー入力に応じてリストを表示およびフィルタリングします。ProductCategoryRow(緑色)は各カテゴリーの見出しを表示します。ProductRow(黄色)は各商品の行を表示します。
ProductTable(ラベンダー色)を見ると、テーブルヘッダー(「Name」と「Price」ラベルを含む)が独自のコンポーネントではないことがわかります。これは好みの問題であり、どちらでも構いません。この例では、それはProductTableの一部です。なぜなら、ProductTableのリスト内に表示されるからです。ただし、このヘッダーが複雑になる場合(例えば、ソート機能を追加する場合)は、独自のProductTableHeaderコンポーネントに移動することができます。
モックアップ内のコンポーネントを特定したので、それらを階層に整理します。モックアップ内で別のコンポーネント内に表示されるコンポーネントは、階層内で子として表示されるべきです:
FilterableProductTableSearchBarProductTableProductCategoryRowProductRow
ステップ2: Reactで静的なバージョンを構築する
コンポーネント階層ができたので、アプリを実装する時です。最も簡単なアプローチは、データモデルからUIをレンダリングするが、まだインタラクティブ性を追加しないバージョンを構築することです…まだです!静的なバージョンを最初に構築し、後でインタラクティブ性を追加する方がしばしば簡単です。静的なバージョンの構築には多くのタイピングが必要で、考えることはほとんどありませんが、インタラクティブ性の追加には多くの思考が必要で、タイピングはあまり必要ありません。
データモデルをレンダリングするアプリの静的なバージョンを構築するには、他のコンポーネントを再利用し、コンポーネントを使用してデータを渡すpropsを使用してデータを渡すコンポーネントを構築したいでしょう。propsは親から子へデータを渡す方法です。(stateの概念に慣れている場合、この静的なバージョンを構築するためにstateを全く使用しないでください。stateはインタラクティブ性、つまり時間とともに変化するデータのためにのみ予約されています。これはアプリの静的なバージョンなので、必要ありません。)
階層の上位にあるコンポーネント(FilterableProductTableなど)の構築から始める「トップダウン」アプローチか、階層の下位にあるコンポーネント(ProductRowなど)から作業する「ボトムアップ」アプローチのどちらかを取ることができます。より単純な例では、通常トップダウンの方が簡単であり、より大きなプロジェクトではボトムアップの方が簡単です。
(このコードが難しそうに見える場合は、まずクイックスタートを読んでください!)
コンポーネントを構築した後は、データモデルをレンダリングする再利用可能なコンポーネントのライブラリができあがります。これは静的なアプリなので、コンポーネントはJSXを返すだけです。階層の最上位にあるコンポーネント(FilterableProductTable)は、データモデルをpropsとして受け取ります。これは一方向データフローと呼ばれます。なぜなら、データが最上位のコンポーネントからツリーの下位のコンポーネントへと流れるからです。
落とし穴
この時点では、状態(state)値はまだ使用しないでください。それは次のステップで行います!
ステップ3: UI状態の最小限かつ完全な表現を見つける
UIをインタラクティブにするには、ユーザーが基礎となるデータモデルを変更できるようにする必要があります。これには状態(state)を使用します。
状態(state)とは、アプリが記憶しておく必要がある変化するデータの最小限のセットと考えてください。状態を構造化する最も重要な原則は、DRY(Don't Repeat Yourself、繰り返しを避ける)を保つことです。アプリケーションが必要とする状態の絶対的最小限の表現を見つけ出し、それ以外のものは必要に応じて計算します。例えば、買い物リストを作成する場合、アイテムを状態として配列に格納できます。リスト内のアイテム数も表示したい場合、アイテム数を別の状態値として保存するのではなく、配列の長さを読み取ります。
では、この例のアプリケーションにあるすべてのデータを考えてみましょう:
- 元の製品リスト
- ユーザーが入力した検索テキスト
- チェックボックスの値
- フィルタリングされた製品リスト
これらのうち、どれが状態(state)でしょうか?状態でないものを特定してください:
- 時間が経っても変化しないか?もしそうなら、それは状態ではありません。
- 親コンポーネントからpropsとして渡されてくるか?もしそうなら、それは状態ではありません。
- 既存の状態やpropsから計算できるか?もしそうなら、それは間違いなく状態ではありません!
残ったものがおそらく状態です。
もう一度一つずつ確認してみましょう:
- 元の製品リストはpropsとして渡されるので、状態ではありません。
- 検索テキストは、時間とともに変化し、他のものから計算できないため、状態のようです。
- チェックボックスの値は、時間とともに変化し、他のものから計算できないため、状態のようです。
- フィルタリングされた製品リストは状態ではありません。なぜなら、元の製品リストを取得し、検索テキストとチェックボックスの値に基づいてフィルタリングすることで計算できるからです。
つまり、状態は検索テキストとチェックボックスの値だけです!よくできました!
ステップ4: Stateをどこに配置すべきか特定する
アプリの最小限のstateデータを特定した後、どのコンポーネントがこのstateを変更する責任を持つか、つまりstateを所有するかを特定する必要があります。覚えておいてください:Reactは一方向のデータフローを使用し、データをコンポーネント階層の親から子へと渡します。どのコンポーネントがどのstateを所有すべきか、すぐには明確でないかもしれません。この概念に慣れていない場合は難しいかもしれませんが、以下の手順に従って見つけ出すことができます!
アプリケーション内の各stateについて:
- そのstateに基づいて何かをレンダリングするすべてのコンポーネントを特定します。
- それらの最も近い共通の親コンポーネント(階層内でそれらすべての上位にあるコンポーネント)を見つけます。
- stateをどこに配置すべきかを決定します:
- 多くの場合、stateを共通の親コンポーネントに直接配置できます。
- 共通の親コンポーネントのさらに上位のコンポーネントにstateを配置することもできます。
- stateを所有するのに適切なコンポーネントが見つからない場合は、stateを保持するためだけの新しいコンポーネントを作成し、共通の親コンポーネントの上位の階層のどこかに追加します。
前のステップで、このアプリケーションには2つのstateがあることがわかりました:検索入力テキストとチェックボックスの値です。この例では、これらは常に一緒に現れるので、同じ場所に配置するのが理にかなっています。
では、それらに対する戦略を実行してみましょう:
- stateを使用するコンポーネントを特定する:
ProductTableはそのstate(検索テキストとチェックボックスの値)に基づいて製品リストをフィルタリングする必要があります。SearchBarはそのstate(検索テキストとチェックボックスの値)を表示する必要があります。
- 共通の親を見つける:両方のコンポーネントが共有する最初の親コンポーネントは
FilterableProductTableです。 - stateを配置する場所を決定する:フィルターテキストとチェック状態の値を
FilterableProductTableに保持します。
したがって、stateの値はFilterableProductTableに配置されます。
コンポーネントにstateを追加するには、useState()フックを使用します。フックはReactに「フックする」ことを可能にする特別な関数です。FilterableProductTableの先頭に2つのstate変数を追加し、それらの初期状態を指定します:
次に、filterTextとinStockOnlyをProductTableとSearchBarにpropsとして渡します:
アプリケーションがどのように動作するか確認できます。以下のサンドボックスのコードで、filterTextの初期値をuseState('')からuseState('fruit')に編集してみてください。検索入力テキストとテーブルの両方が更新されるのがわかります:
フォームを編集してもまだ動作しないことに注意してください。上のサンドボックスにはその理由を説明するコンソールエラーがあります:
コンソール
フォームフィールドに `onChange` ハンドラなしで `value` プロップを提供しました。これにより読み取り専用フィールドがレンダリングされます。
上のサンドボックスでは、ProductTableとSearchBarがfilterTextとinStockOnlyプロップを読み取り、テーブル、入力フィールド、チェックボックスをレンダリングします。例えば、SearchBarが入力値に値を設定する方法は次のとおりです:
しかし、ユーザーのタイピングなどの操作に応答するコードはまだ追加されていません。これが最後のステップになります。
ステップ5:逆方向のデータフローを追加する
現在、アプリはプロップと状態が階層を下って流れることで正しくレンダリングされています。しかし、ユーザー入力に応じて状態を変更するには、データが反対方向に流れることをサポートする必要があります:階層の深いところにあるフォームコンポーネントがFilterableProductTableの状態を更新する必要があります。
Reactはこのデータフローを明示的にしますが、双方向データバインディングよりも少し多くの記述が必要です。上の例で入力しようとしたりチェックボックスをクリックしたりすると、Reactが入力を無視することがわかります。これは意図的です。<input value={filterText} />と記述することで、inputのvalueプロップを、FilterableProductTableから渡されるfilterText状態と常に等しくなるように設定しました。filterText状態が設定されることがないため、入力は決して変化しません。
ユーザーがフォーム入力を変更するたびに、状態がそれらの変更を反映するように更新されるようにしたいと考えています。状態はFilterableProductTableが所有しているため、setFilterTextとsetInStockOnlyを呼び出せるのはそれだけです。SearchBarがFilterableProductTableの状態を更新できるようにするには、これらの関数をSearchBarに渡す必要があります:
SearchBar内部で、onChangeイベントハンドラを追加し、それらから親の状態を設定します:
これでアプリケーションは完全に動作します!
イベントの処理や状態の更新については、インタラクティビティの追加セクションですべて学ぶことができます。
次に進むべき場所
これは、Reactでコンポーネントとアプリケーションを構築する考え方についての非常に簡潔な紹介でした。今すぐReactプロジェクトを開始するか、またはこのチュートリアルで使用されたすべての構文についてさらに深く掘り下げることができます。
