3秒でわかる
データは親から子へ流し、変更は子から親へ通知する設計の原則。状態の出どころを 1 か所に保てるので、表示がおかしいときの原因を辿りやすくなります。
もう少し詳しく
どういうものか
単方向データフローは、値の流れる向きを 1 方向に固定する考え方です。React では、状態を持つのは親コンポーネントで、子には props として値を渡します。子は受け取った値を直接書き換えず、渡された関数を呼んで親に知らせます。
function Parent() {
const [count, setCount] = useState(0);
return <Child count={count} onAdd={() => setCount(c => c + 1)} />;
}
function Child({ count, onAdd }) {
return <button onClick={onAdd}>{count} 個</button>;
}値は下へ、通知は上へ。行き来するのはこの 2 本だけで、子が親の状態を直接触ることはありません。React の props が読み取り専用になっているのは、この形を守らせるためです。
なぜ必要か
画面がおかしくなったとき、探す場所が決まるからです。表示は props の値をそのまま映しているだけなので、値が変なら親の state を見ればよい、と一直線に辿れます。どこからでも書き換えられる設計だと、同じ値を 3 か所が別々に更新していて、順番によって結果が変わる、という追いにくいバグが生まれます。
つまずきやすいところ
props を直接書き換えてしまう間違いが最初に来ます。
function Child({ items }) {
items.<a href="/glossary/push" class="text-primary font-medium underline underline-offset-2 hover:text-primary-dark">push</a>("new"); // 親の<a href="/glossary/array" class="text-primary font-medium underline underline-offset-2 hover:text-primary-dark">配列</a>を壊している
return <List items={items} />;
}配列やオブジェクトは参照が渡るので、push や代入は親のデータそのものに届きます。元を変えずに新しい配列を作り、親に渡された関数経由で反映します。
もうひとつ、props で受け取った値を useState の初期値にコピーする書き方も詰まります。初期値は最初の 1 回しか読まれないため、親が値を変えても子は古いまま表示し続けます。表示するだけなら state に持たず、props をそのまま使います。
似た用語との違い
双方向は書く量が減りますが、値がどこで変わったのかが見えにくくなります。React や Redux が単方向を選んでいるのは、書く量の少なさより、後から追える読みやすさを取ったためです。人数が増えるほどこの差が効いてきます。
覚え方
「水は上から下に流れ、報告は下から上に上がる」。値を子から親へ押し戻したくなったら、状態の置き場所が間違っているという合図です。