場合によっては、2つのコンポーネントの状態を常に一緒に変更したいことがあります。そのためには、両方のコンポーネントから状態を削除し、それらを最も近い共通の親コンポーネントに移動し、props を介してそれらに渡します。これは状態の持ち上げと呼ばれ、React コードを書く際に最も一般的な操作の1つです。
学習内容
- 状態を上げてコンポーネント間で共有する方法
- 制御されたコンポーネントと制御されていないコンポーネントとは何か
例による状態の持ち上げ
この例では、親の Accordion
コンポーネントが2つの別々の Panel
をレンダリングします。
アコーディオン
パネル
パネル
それぞれの Panel
コンポーネントは、コンテンツが表示されるかどうかを決定するブール値の isActive
状態を持っています。
両方のパネルの「表示」ボタンを押してください
import { useState } from 'react'; function Panel({ title, children }) { const [isActive, setIsActive] = useState(false); return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={() => setIsActive(true)}> Show </button> )} </section> ); } export default function Accordion() { return ( <> <h2>Almaty, Kazakhstan</h2> <Panel title="About"> With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. </Panel> <Panel title="Etymology"> The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple. </Panel> </> ); }
一方のパネルのボタンを押しても、もう一方のパネルには影響がないことに注意してください。— それらは独立しています。


最初は、それぞれの Panel
の isActive
状態は false
なので、どちらも折りたたまれた状態で表示されます。


いずれかの Panel
のボタンをクリックすると、その Panel
の isActive
状態のみが更新されます。
しかし、今度はいつでも1つのパネルだけを展開するように変更したいとします。 その設計では、2番目のパネルを展開すると、最初のパネルが折りたたまれる必要があります。どうすればいいでしょうか?
これらの2つのパネルを調整するには、3つの手順で状態を親コンポーネントに「持ち上げる」必要があります。
- 子コンポーネントから状態を削除します。
- 共通の親からハードコードされたデータを渡します。
- 共通の親に状態を追加し、イベントハンドラと一緒に渡します。
これにより、Accordion
コンポーネントは両方の Panel
を調整し、一度に1つだけ展開することができます。
手順 1: 子コンポーネントから状態を削除する
Panel
の isActive
の制御を親コンポーネントに渡します。これは、親コンポーネントが isActive
をpropとして Panel
に渡すことを意味します。Panel
コンポーネントから この行を削除することから始めます。
const [isActive, setIsActive] = useState(false);
そして、代わりに isActive
を Panel
の props のリストに追加します。
function Panel({ title, children, isActive }) {
これで、Panel
の親コンポーネントは、propとして渡すことで isActive
を制御できるようになりました。逆に、Panel
コンポーネントは isActive
の値を制御できなくなりました。— 親コンポーネント次第です!
手順 2: 共通の親からハードコードされたデータを渡す
状態を上げるには、調整したい両方の子コンポーネントの最も近い共通の親コンポーネントを見つける必要があります。
Accordion
(最も近い共通の親)パネル
パネル
この例では、Accordion
コンポーネントが該当します。これは両方のパネルの上に位置し、それらのプロパティを制御できるため、現在アクティブなパネルの「真実の源」となります。Accordion
コンポーネントがハードコードされた isActive
の値(例えば、true
)を両方のパネルに渡すようにします。
import { useState } from 'react'; export default function Accordion() { return ( <> <h2>Almaty, Kazakhstan</h2> <Panel title="About" isActive={true}> With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. </Panel> <Panel title="Etymology" isActive={true}> The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple. </Panel> </> ); } function Panel({ title, children, isActive }) { return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={() => setIsActive(true)}> Show </button> )} </section> ); }
Accordion
コンポーネント内のハードコードされた isActive
値を編集して、画面上の結果を確認してください。
ステップ 3: 共通の親に状態を追加する
状態を上に持ち上げることで、状態として保存しているものの性質が変わる事がよくあります。
この場合、一度にアクティブにできるパネルは1つだけです。これは、Accordion
の共通の親コンポーネントが、*どの* パネルがアクティブであるかを追跡する必要があることを意味します。ブール値の代わりに、アクティブな Panel
のインデックスとして数値を状態変数に使用できます。
const [activeIndex, setActiveIndex] = useState(0);
activeIndex
が 0
の場合、最初のパネルがアクティブになり、1
の場合は、2番目のパネルがアクティブになります。
いずれかの Panel
の「表示」ボタンをクリックすると、Accordion
のアクティブなインデックスが変更されます。Panel
は Accordion
内で定義されているため、activeIndex
状態を直接設定できません。Accordion
コンポーネントは、イベントハンドラーをプロパティとして渡すことで、Panel
コンポーネントがその状態を変更することを*明示的に許可*する必要があります。
<>
<Panel
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
...
</Panel>
<Panel
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
...
</Panel>
</>
Panel
内の <button>
は、クリックイベントハンドラーとして onShow
プロパティを使用するようになります。
import { useState } from 'react'; export default function Accordion() { const [activeIndex, setActiveIndex] = useState(0); return ( <> <h2>Almaty, Kazakhstan</h2> <Panel title="About" isActive={activeIndex === 0} onShow={() => setActiveIndex(0)} > With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. </Panel> <Panel title="Etymology" isActive={activeIndex === 1} onShow={() => setActiveIndex(1)} > The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple. </Panel> </> ); } function Panel({ title, children, isActive, onShow }) { return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={onShow}> Show </button> )} </section> ); }
これで状態の持ち上げは完了です!状態を共通の親コンポーネントに移動することで、2つのパネルを連携させることができました。「表示されているか」を示す2つのフラグの代わりにアクティブなインデックスを使用することで、一度に1つのパネルのみがアクティブになることが保証されます。また、イベントハンドラーを子に渡すことで、子が親の状態を変更できるようになりました。


初期状態では、Accordion
の activeIndex
は 0
なので、最初の Panel
は isActive = true
を受け取ります。


Accordion
の activeIndex
状態が 1
に変わると、2番目の Panel
は代わりに isActive = true
を受け取ります。
詳細
ローカル状態を持つコンポーネントを「制御されていない」と呼ぶことはよくあります。たとえば、isActive
状態変数を持つ元の Panel
コンポーネントは、親がパネルがアクティブであるかどうかを制御できないため、制御されていません。
対照的に、コンポーネント内の重要な情報が、独自のローカル状態ではなくプロパティによって駆動される場合、そのコンポーネントは「制御されている」と言うことができます。これにより、親コンポーネントはその動作を完全に指定できます。isActive
プロパティを持つ最終的な Panel
コンポーネントは、Accordion
コンポーネントによって制御されます。
制御されていないコンポーネントは、設定が少なくて済むため、親の中で使いやすくなっています。ただし、それらをまとめて調整する場合、柔軟性が低くなります。制御されたコンポーネントは最大限の柔軟性を備えていますが、親コンポーネントがプロパティを使用して完全に設定する必要があります。
実際には、「制御された」と「制御されていない」は厳密な技術用語ではなく、各コンポーネントには通常、ローカル状態とプロパティの両方が混在しています。ただし、これは、コンポーネントの設計方法と提供される機能について説明するのに便利な方法です。
コンポーネントを作成するときは、その中のどの情報を制御する必要があるか(プロパティ経由)、どの情報を制御しないでおく必要があるか(状態経由)を検討してください。ただし、いつでも考えを変えて、後でリファクタリングできます。
各状態の単一の真実の源
Reactアプリケーションでは、多くのコンポーネントが独自のステートを持ちます。入力欄のように、一部のステートはツリーの最下部にあるリーフコンポーネントの近くに「存在」する場合があります。他のステートは、アプリの上部に「存在」する場合があります。たとえば、クライアントサイドのルーティングライブラリでさえ、通常は現在のルートをReactステートに格納し、propsによってそれを下位に渡すことで実装されます!
それぞれの固有のステートについて、それを「所有」するコンポーネントを選択します。この原則は、「単一の情報源」(“Single Source of Truth”)を持つこととしても知られています。これは、すべてのステートが1か所に存在することを意味するのではなく、*各*ステートについて、その情報を保持する*特定の*コンポーネントがあることを意味します。コンポーネント間で共有ステートを複製する代わりに、共通の親コンポーネントに*持ち上げて*、それを必要とする子コンポーネントに*渡します*。
アプリは作業を進めるにつれて変化します。それぞれのステートがどこに「存在」するのかをまだ理解している段階では、ステートを下位または上位に移動することはよくあることです。これはすべてプロセスの一部です!
もう少し多くのコンポーネントでこれが実際にどのように感じられるかを確認するには、Thinking in Reactをご覧ください。
要約(リンクアイコン)
- 2つのコンポーネントを連携させたい場合は、それらのステートを共通の親コンポーネントに移動します。
- 次に、共通の親コンポーネントからpropsを介して情報を下位に渡します。
- 最後に、子コンポーネントが親コンポーネントのステートを変更できるように、イベントハンドラを下位に渡します。
- コンポーネントを「制御された」(propsによって駆動される)または「制御されていない」(ステートによって駆動される)と考えることは役立ちます。
チャレンジに挑戦してみましょう(リンクアイコン)
チャレンジ 1/ 2: 同期入力(リンクアイコン)
これら2つの入力は独立しています。これらを同期させてください。一方の入力を編集すると、もう一方の入力も同じテキストで更新され、その逆も同様です。
import { useState } from 'react'; export default function SyncedInputs() { return ( <> <Input label="First input" /> <Input label="Second input" /> </> ); } function Input({ label }) { const [text, setText] = useState(''); function handleChange(e) { setText(e.target.value); } return ( <label> {label} {' '} <input value={text} onChange={handleChange} /> </label> ); }