状態の管理
アプリケーションが成長するにつれて、状態の整理方法やコンポーネント間でのデータの流れをより意図的に設計することが重要になります。冗長または重複した状態は、バグの一般的な原因です。この章では、状態を適切に構造化する方法、状態更新ロジックを保守しやすく保つ方法、離れたコンポーネント間で状態を共有する方法について学びます。
状態を用いた入力への反応
Reactでは、コードから直接UIを変更することはありません。例えば、「ボタンを無効にする」「ボタンを有効にする」「成功メッセージを表示する」などのコマンドを記述することはありません。代わりに、コンポーネントの異なる視覚的状態(「初期状態」「入力中状態」「成功状態」)に対して表示したいUIを記述し、ユーザー入力に応じて状態変化をトリガーします。これはデザイナーがUIを考える方法に似ています。
以下はReactを使用して構築されたクイズフォームです。status状態変数を使用して、送信ボタンを有効にするか無効にするか、成功メッセージを代わりに表示するかを決定していることに注目してください。
状態の構造の選択
状態を適切に構造化することは、修正やデバッグが容易なコンポーネントと、常にバグの原因となるコンポーネントとの違いを生みます。最も重要な原則は、状態に冗長または重複した情報を含めないことです。不要な状態があると、更新を忘れやすく、バグを引き起こす可能性があります!
例えば、このフォームには冗長なfullName状態変数があります:
これを削除し、コンポーネントのレンダリング中にfullNameを計算することでコードを簡略化できます:
これは小さな変更のように見えるかもしれませんが、Reactアプリの多くのバグはこのように修正されます。
コンポーネント間での状態の共有
2つのコンポーネントの状態を常に一緒に変更したい場合があります。そのためには、両方のコンポーネントから状態を取り除き、それらに最も近い共通の親コンポーネントに移動し、propsを通じて子コンポーネントに渡します。これは「状態のリフトアップ」として知られており、Reactコードを書く際に最も一般的に行うことの一つです。
この例では、一度にアクティブになるパネルは1つだけです。これを実現するために、各パネル内部にアクティブ状態を保持する代わりに、親コンポーネントが状態を保持し、子コンポーネントにpropsを指定します。
状態の保持とリセット
コンポーネントを再レンダリングする際、Reactはツリーのどの部分を保持(および更新)し、どの部分を破棄または最初から再作成するかを決定する必要があります。ほとんどの場合、Reactの自動的な動作は十分に機能します。デフォルトでは、Reactは以前にレンダリングされたコンポーネントツリーと「一致する」ツリーの部分を保持します。
しかし、これが望ましくない場合もあります。このチャットアプリでは、メッセージを入力してから受信者を切り替えても、入力がリセットされません。これにより、ユーザーが誤って間違った相手にメッセージを送信してしまう可能性があります:
Reactでは、デフォルトの動作をオーバーライドし、異なる強制できます。これは、受信者が異なる場合、それは新しいデータ(および入力などのUI)で最初から再作成する必要があるkeyを渡すことで(例:<Chat key={email} />)、コンポーネントに状態をリセットすることを異なるChatコンポーネントと見なすべきだとReactに伝えます。これにより、受信者間を切り替えると、同じコンポーネントをレンダリングしているにもかかわらず、入力フィールドがリセットされます。
状態ロジックをリデューサーに抽出する
多くのイベントハンドラに分散した多くの状態更新を持つコンポーネントは、圧倒される可能性があります。このような場合、すべての状態更新ロジックをコンポーネントの外側の単一の関数(「リデューサー」と呼ばれます)に統合することができます。イベントハンドラは、ユーザーの「アクション」のみを指定するため簡潔になります。ファイルの下部では、リデューサー関数が各アクションに応じて状態をどのように更新すべきかを指定します!
Contextを使用したデータの深い受け渡し
通常、親コンポーネントから子コンポーネントへ情報を渡すにはpropsを使用します。しかし、多くのコンポーネントを経由してpropsを渡す必要がある場合や、多くのコンポーネントが同じ情報を必要とする場合、propsの受け渡しは不便になることがあります。Contextを使用すると、親コンポーネントは、その下のツリー内の任意のコンポーネント(どれだけ深くても)に対して、propsを明示的に渡すことなく情報を利用可能にすることができます。
ここでは、Headingコンポーネントが、最も近いSectionにそのレベルを「尋ねる」ことで見出しレベルを決定します。各Sectionは、親のSectionに尋ねて1を加えることで、自身のレベルを追跡します。すべてのSectionは、propsを渡すことなく、その下のすべてのコンポーネントに情報を提供します。これはContextを通じて行われます。
リデューサーとContextによるスケールアップ
リデューサーを使用すると、コンポーネントの状態更新ロジックを統合できます。Contextを使用すると、情報を他のコンポーネントの深い階層に渡すことができます。リデューサーとContextを組み合わせることで、複雑な画面の状態を管理できます。
このアプローチでは、複雑な状態を持つ親コンポーネントがリデューサーを使用して状態を管理します。ツリーの深い階層にある他のコンポーネントは、Contextを介してその状態を読み取ることができます。また、その状態を更新するためのアクションをディスパッチすることもできます。
次は何をしますか?
状態を用いた入力への反応に進んで、この章をページごとに読み始めましょう!
あるいは、これらのトピックにすでに精通しているなら、エスケープハッチについて読んでみませんか?
