導入
これまで state は 1 つのコンポーネントの中に持っていました。でも実務では「入力欄に打った文字を、別の場所のプレビューにも反映したい」のように、複数のコンポーネントで同じ値を共有したい場面がたくさんあります。
React には兄弟どうしで直接データを渡す仕組みはありません。そこで使うのが定番テクニック 「状態を上げる(Lifting State Up)」 です。合言葉は 「stateは、それを使う全員の”共通の親”に置く」。
説明
共有したい値(text)を親に持たせ、下の図のように配ります。値は props で下へ流し、変更のきっかけ(イベント)は関数を渡して上へ返します。
flowchart TB parent["親 App<br/>state: text を持つ(唯一の置き場)"] input["子 InputBox<br/>入力する"] preview["子 Preview<br/>表示する"] parent -->|"text と setText を props で渡す(下へ)"| input parent -->|"text を props で渡す(下へ)"| preview input -.->|"onChange で setText を呼ぶ(上へ)"| parent
「入力欄」と「プレビュー」が、親の 1 つの state を共有する例です。
// 子①:入力する部品(自分ではstateを持たず、親から受け取って使う)
function InputBox({ text, onChange }) {
return (
<input
value={text}
onChange={(e) => onChange(e.target.value)}
placeholder="なにか入力"
/>
);
}
// 子②:表示する部品(同じ値を受け取って映すだけ)
function Preview({ text }) {
return <p>プレビュー: <strong>{text || "(まだ空です)"}</strong></p>;
}
// 親:state はここ 1 か所だけ。子ふたりに配る。
function App() {
const [text, setText] = React.useState("");
return (
<div style={{ fontFamily: "system-ui", maxWidth: 320 }}>
<h2>状態を上げる</h2>
<InputBox text={text} onChange={setText} />
<Preview text={text} />
<p>文字数: {text.length}</p>
</div>
);
}
textを持つのはAppだけ。InputBoxもPreviewも自分では state を持ちません。- 親は
text(値)とsetText(更新関数)を props で下へ渡します。 - 入力があると
InputBoxはonChange(=親のsetText)を呼び、更新を 上へ伝えます。 - state が変わると、それを使っている
InputBox・Preview・文字数表示がまとめて最新になります。
これが「単方向データフロー」――データは上から下へ、変更の通知は下から上へという、React の一番大事な考え方です。
やってみよう
Previewをもう 1 つ増やしても、同じtextを渡すだけで両方が同期することを確かめましょう。- 大文字に変換して表示する
<p>大文字: {text.toUpperCase()}</p>をAppに足してみましょう。1 つの state から、いくつでも表示を作れます。