概要
状態管理とは「変数を持つこと」ではない。
それは**“いつ・どこで・なぜ・どのように変わるか”を制御する設計行為**である。
JavaScriptにおいて、状態はコンポーネント内・グローバルストア・URL・DOM属性など複数の場所に分布する。
この分布を制御せずに進むと、バグの温床・パフォーマンスの劣化・テスト不能な構造が生まれる。
本稿では、状態のスコープ設計・責務分離・同期の設計・再レンダリング最適化を主軸に、破綻しない状態管理の構築戦略を解説する。
1. 状態スコープは「責務」で分ける
| 状態 | スコープ | 例 |
|-------------|----------------|--------------------------------|
| UI状態 | ローカル | フォーム入力、モーダル開閉 |
| アプリ状態 | グローバル | 認証ユーザー、テーマ、言語 |
| 一時状態 | URL or storage | ページ番号、ソート条件 |
- ✅ 状態の種類によって保存先と責任範囲を分離
- ✅ 状態がどこにあるかではなく、なぜそこにあるかを定義する
2. ローカル状態(コンポーネント内部)の適切な管理
const [value, setValue] = useState('');
- ✅ UIと密接に関係する状態はローカルに閉じ込める
- ❌ 親 → 子 → 孫に props-drilling して破綻 → Context or Store へ昇格検討
3. グローバル状態(Context / Store)の分離と集約
// e.g. Zustand / Redux / Pinia (Vue)
const useAuthStore = create((set) => ({
user: null,
setUser: (u) => set({ user: u }),
}));
- ✅ 1機能 = 1ストアの原則(例:Auth / User / Settings)
- ✅ ドメイン単位で責務を明確にする
4. 非同期状態と整合性保証
useEffect(() => {
let cancel = false;
async function fetchData() {
const res = await fetch('/api/data');
if (!cancel) setData(res);
}
fetchData();
return () => (cancel = true);
}, []);
- ✅ 非同期は race condition を考慮する設計に
- ✅ UI更新は “最後に呼ばれた非同期” だけが責任を持つように
5. 再レンダリングの最小化と selector分離
const count = useStore((s) => s.counter);
- ✅ 必要なstateだけ抽出(selector設計)
- ✅ 不要な再描画を避けるため、stateの分解と構造化が必須
6. 状態の可視化とデバッグ
- ✅ Redux DevTools / Zustand Devtools などを活用
- ✅ stateの変更履歴を追える構造は設計の証明になる
設計判断フロー
① 状態はUIに閉じているか? → useState / local に保つ
② 複数コンポーネントが参照しているか? → グローバル状態に昇格
③ 状態が非同期と絡むか? → 整合性保証とキャンセル設計
④ 状態更新が不要に描画を発生させていないか? → selectorや分離の導入
⑤ 状態の出入りが可視化されているか? → Devtoolsまたはログ設計
よくあるミスと対策
❌ どのコンポーネントがどのstateを持っているか把握できない
→ ✅ 責務ごとに分離・昇格 / 降格ルールを持つ
❌ 状態更新が全体再レンダリングを引き起こす
→ ✅ stateの粒度を細分化 + selector設計で分離
❌ 非同期状態で古い結果が画面を上書きする
→ ✅ キャンセル設計 or fetch番号管理で整合性保証
結語
状態とは「値を持つこと」ではない。
それは**“今を記憶し、未来の動作を制御し、過去を辿れる構造”**である。
- スコープを切り分け
- 責務ごとに昇格 / 降格を設計し
- 非同期と同期の整合性を制御し
- 再レンダリングを抑えて、構造を守る
JavaScriptにおける状態管理とは、
“UIとロジックの架け橋を構造化し、破綻を防ぐための制御戦略”である。