useStateで状態がネストしすぎた構造のレビュー指針
ReactでuseState
を使う際、「複数の状態をまとめたい」「オブジェクトで扱いたい」という理由から、ネストした構造を選ぶ実装がしばしば見られます。しかし、ネスト構造は再レンダリングの予測困難性、責務の不明確化、更新の煩雑さといった問題を内包しており、レビュー観点では設計見直しの要対象です。
本稿では、ネストされたuseState
構造がどのような問題を引き起こしうるか、そしてレビューアーとしてどのように読み取り、指摘すべきかを実例を交えて解説します。
なぜネスト構造が問題なのか
一見整理されているように見えるネスト構造は、以下のような欠点を持ちます。
- 局所的な変更が難しい(プロパティ単位でのsetStateが煩雑)
- 再レンダリングの意図が曖昧になる(どの値が変わったか分からない)
- 責務が不透明(一つのstateが複数の用途を兼ねている)
- コンポーネント分離がしづらくなる(propsで渡す際の構造が肥大化)
これらの問題は、UIの拡張やロジックの追加によって後から顕在化しやすく、初期実装段階では見過ごされがちです。
ネスト状態の典型例
const [formState, setFormState] = useState({
user: {
name: "",
age: 0,
},
address: {
zip: "",
city: "",
},
editing: false,
});
@Reviewer: 状態のネストにより、責務が`user`・`address`・`編集フラグ`で混在しています。部分的な更新が複雑化し、変更の意図もコードから読み取りづらくなります。責務単位でstateを分割する設計を検討してください。
このような構造では、たとえば address.city
だけを更新する場合にもsetFormState
でオブジェクトを再構築しなければならず、不必要な再レンダリングとバグの温床になります。
再レンダリングに与える影響
Reactはstateの再設定によって再レンダリングが発生しますが、オブジェクトの比較はshallow(浅い)で行われるため、ネストされた内部プロパティの変更も「構造全体が変更された」とみなされます。
たとえば以下のような書き方でも、user.name
を変えるたびにaddress
関連のUIもすべて再描画対象になる可能性があります。
setFormState(prev => ({
...prev,
user: { ...prev.user, name: "新しい名前" },
}));
これにより、変更とは無関係なサブツリーまで再レンダリングされる構造が発生し、パフォーマンス上の問題やバグを誘発します。
責務単位で分割する構造例
Reactでは、責務ごとに状態を分割する設計が最も自然です。
const [user, setUser] = useState({ name: "", age: 0 });
const [address, setAddress] = useState({ zip: "", city: "" });
const [editing, setEditing] = useState(false);
@Reviewer: 状態が「ユーザー情報」「住所」「編集フラグ」に明確に分離され、責務単位での更新が容易になっています。再レンダリングの最小化にも有効です。
状態が責務単位で分離されていれば、useEffectやuseCallbackの依存配列にも正しく反映されるため、副作用の制御やメモ化最適化の精度も上がります。
Component分割との親和性
状態がネストされていると、propsとして渡す構造も複雑化し、コンポーネント分割が難しくなります。
<ProfileForm formState={formState} setFormState={setFormState} />
このような構造では、子コンポーネント側の実装もフォーム構造に強く依存するため、再利用性・保守性が著しく低下します。
一方、状態が責務単位であれば以下のように個別に渡すことができ、構造が明確になります。
<ProfileForm
user={user}
setUser={setUser}
address={address}
setAddress={setAddress}
/>
これにより、子コンポーネント側でも責務単位で関心を持てる設計になります。
状態構造の整理は型レベルで抑制する
TypeScriptなど型付き言語を使用している場合、状態構造のネスト化を型の定義であえて避ける方針も有効です。
type User = { name: string; age: number };
type Address = { zip: string; city: string };
このように設計しておくことで、「フォーム全体=1つの構造」という設計を自然に避け、型の時点で責務分離を強制する設計指針が取れます。
まとめ:ネスト構造をレビューするための5つの観点
ネストされたuseState構造に遭遇した際、レビューアーとしては次の観点で構造を読み解き、判断していくことが求められます。
- 1つのstateが複数の意味や責務を兼ねていないか
- 一部の値更新で構造全体が再構築されていないか
- 再レンダリングの最小化が意識された構造か
- propsとして渡す際に構造が肥大化していないか
- 型定義がネスト設計を助長していないか
useStateは柔軟であるがゆえに、設計の甘さが構造の肥大や保守性低下につながりやすいポイントです。レビューアーは表面的な動作だけでなく、構造の将来性・拡張性・責務の明示性といった観点から、設計判断の是非を見抜く力が求められます。