環境
React 18.2.0
はじめに
Reactは双方向バインディングではなく、単方向のデータフローを採用しています。
双方向バインディングだと、データの状態がどのように変更されているかや、状態管理が複雑になる問題がある一方、単方向データフローだとその状態を管理しているコンポーネントのみが変更することができるので、デバッグや状態管理が容易になるという利点があります。
そのためコンポーネント間で状態を管理する場合は、「Stateのリフトアップ」をする必要があります。
要するに「Stateのリフトアップ」とは複数のコンポーネント間で共有される状態を、上位の親コンポーネントで管理することです。
サンプル
今回は例として、郵便番号を入力し完了次第、都道府県、市が表示されるという画面を作成します。
親コンポーネントから作成していきます。
import { Card } from "./Card";
import { ZipCodeInputText } from "./ZipCodeInputText";
export const Parent = () => {
return (
<>
<ZipCodeInputText />
<Card />
</>
);
};
そして子コンポーネントに該当する郵便番号のInput要素と、結果を表示するコンポーネントを作成します。
import { useState } from "react";
export const ZipCodeInputText = () => {
const [zipcode, setZipCode] = useState("");
return (
<div style={{ margin: "10px 0" }}>
<label htmlFor="zipcode" style={{ marginRight: "10px" }}>
郵便番号 :
</label>
<input
name="zipcode"
type="text"
value={zipcode}
onChange={(event) => setZipCode(event.target.value)}
/>
</div>
);
};
const Addresses = [
{
zipcode: "100001",
prefecture: "東京都",
city: "千代田区"
}
];
export const Card = ({ zipcode }) => {
const address = Addresses.find((address) => address.zipcode === zipcode);
return (
<div>
<p>都道府県 : {address?.prefecture}</p>
<p>市 : {address?.city}</p>
</div>
);
};
ZipCodeInputText
では郵便番号を、コンポーネントの内部で制御されるState
でzipcodeとして定義して管理しています。
ここで今回の仕様について確認しましょう。
都道府県と市は郵便番号によって、表示されるという仕様でした。
そのため、Card
コンポーネントは郵便番号という状態を知る必要がありそうです。
これがコンポーネント間で状態を管理するケースに該当します。
現時点の実装ではこれらの仕様を再現するのは厳しいため、「Stateのリフトアップ」利用して実装し直していきましょう。
実装
やり方は簡単です。導入で説明した通り、上位の親コンポーネントで管理すれば良いのです。
それでは実装していきましょう。
import { useState } from "react";
import { Card } from "./Card";
import { ZipCodeInputText } from "./ZipCodeInputText";
export const Parent = () => {
const [zipcode, setZipCode] = useState("");
const updateZipCode = (zipcode) => {
setZipCode(zipcode);
};
return (
<>
<ZipCodeInputText update={updateZipCode} />
<Card zipcode={zipcode} />
</>
);
};
export const ZipCodeInputText = ({ update }) => {
return (
<div style={{ margin: "10px 0" }}>
<label htmlFor="zipcode" style={{ marginRight: "10px" }}>
郵便番号 :
</label>
<input
name="zipcode"
type="text"
onChange={(event) => update(event.target.value)}
/>
</div>
);
};
Card
コンポーネントは変更がないためそのままです。
郵便番号を親コンポーネントであるParent
で定義し、更新関数を渡して入力された郵便番号で更新するようにしました。
これによって、コンポーネント間で状態を管理することが可能になりました。
おわりに
「Stateのリフトアップ」という言葉をよく聞き、その実体についてあまりわかっていなかったので今回記事にしました。
おそらくほとんどの人が無意識のうちにやっていることだと思います。私もそうでした。
ただ複雑なUIになってくるとグローバルで管理しようとしたりするので、その際は「「Stateのリフトアップ」で解決できないか検討しましょう。
そうすることで、状態の管理がしやすくなったり、再利用しやすいコンポーネントを作成することができるため、保守性の向上にもつながるかと思います。
参考ドキュメント