第八章:useStateでは限界を超えたとき - Zustand状態管理マスター
あなたの管理システムが1、2ページしかない場合、第1章で学んだuseStateで十分です。
しかし、システムが大きくなるにつれて、大きな問題に直面するかもしれません:
ダッシュボードページで打刻APIを呼び出し、punchRecordsという配列を取得したとします。
チャートコンポーネントはこの配列を使ってグラフを描画する必要があり、データテーブルコンポーネントはリスト表示に使い、サイドバーは今日の遅刻者数を計算するために必要です。
useStateだけを使う場合、このpunchRecordsを最上位の親コンポーネントから子コンポーネントへ、propsを介してリレーのように次々に渡さなければなりません。
この現象は業界で**「Prop Drilling(プロパティドリリング)」**と呼ばれています。これはコードを複雑にし、途中のどこかで渡し忘れるとシステム全体が壊れてしまいます!
この問題を解決するためには、「グローバル状態管理(Global State Management)」が必要です。
🎯 本章の目標
- グローバル状態管理の概念を理解する:Store(ストア)とは何か?
- Reactコミュニティで最も推奨され、軽量な状態管理ライブラリZustandを学ぶ。
- ログインユーザー情報を管理するグローバルな
userStoreを作成する。 - 全く関係のない2つのコンポーネント間で、同じグローバルデータを読み書きする方法を学ぶ。
🐻 なぜ老舗のReduxではなくZustandを選ぶのか?
5年前のReactチュートリアルを調べると、必ずReduxを使うように教えています。
Reduxは強力ですが、小さな変数を保存するだけでも数十行のボイラープレートコードが必要で、Reducer、Dispatch、Actionといった人間味のない専門用語が溢れています。
Zustand(ドイツ語で「状態」の意味)は可愛い熊です。その設計理念は「シンプルであるほど良い」です。 Zustandはクラウド上に「共有の大倉庫(Store)」を作成し、どのコンポーネントも対応する鍵(Hook)さえ持っていれば、直接倉庫からデータを取得したり変更したりできます。親コンポーネントを介す��必要はまったくありません!
📦 ステップ1:AIに倉庫を作成してもらう
🔥【Vibe Prompt実戦呪文】
React(Vite)でZustandをグローバル状態管理として使用したい。1. npmインストールコマンドを教えてください。2. src/store/にuserStore.tsを作成してください。3. このストアにはuserInfo(name、role、avatarフィールドを含むオブジェクト、デフォルトはnull)を含めてください。4. 状態を更新する2つの関数を提供してください:setUserInfo(ログイン時にデータを書き込む)とclearUser(ログアウト時にデータをクリアする)。5. TypeScriptの型定義を追加して、エディターで自動補完が効くようにしてください。
AIはターミナルを開いてインストールするように教えてくれます:
npm install zustand
そしてこの非常にエレガントなストア設定ファイルを生成します:
// 📂 src/store/userStore.ts
import { create } from 'zustand';
// 1. 倉庫の中身を定義(TypeScript型定義)
interface UserInfo {
name: string;
role: string;
avatar?: string;
}
interface UserState {
userInfo: UserInfo | null;
setUserInfo: (user: UserInfo) => void;
clearUser: () => void;
}
// 2. この大倉庫を作成して公開
export const useUserStore = create<UserState>((set) => ({
userInfo: null, // ウェブページを開いた時点では誰もログインしていない
// データを書き込む魔法:set()関数を呼び出して倉庫を更新
setUserInfo: (user) => set({ userInfo: user }),
// データをクリアする魔法
clearUser: () => set({ userInfo: null }),
}));
🚀 ステップ2:どこからでも「自由にアクセス」
今、この倉庫はあなたのウェブサイトの上空に浮かんでいます。サイトのどこからでもデータを取得できます!
シナリオA:ナビゲーションバー(Navbar.tsx)で名前を取得
import { useUserStore } from '../store/userStore';
export default function Navbar() {
// 先ほど作成したHookを直接呼び出し、「userInfoだけ欲しい」と伝える
const userInfo = useUserStore((state) => state.userInfo);
return (
<nav className="bg-white p-4 flex justify-between shadow-sm">
<div className="font-bold">打刻システム管理画面</div>
{/* ログイン状態に応じて異なるテキストを表示 */}
<div className="text-blue-600">
こんにちは、{userInfo ? userInfo.name : 'ゲスト'}さん
</div>
</nav>
);
}
シナリオB:ログインページ(Login.tsx)でデータを書き込む
import { useUserStore } from '../store/userStore';
export default function Login() {
// 今回は倉庫に「setUserInfoという書き込み関数が欲しい」と伝える
const setUserInfo = useUserStore((state) => state.setUserInfo);
const handleFakeLogin = () => {
// APIログインが成功したと仮定し、直接グローバル倉庫にデータを保存!
setUserInfo({ name: '王社長', role: 'admin' });
alert('ログイン成功!上記のNavbarの名前が変わったか確認してください');
};
return (
<button onClick={handleFakeLogin} className="bg-blue-500 text-white p-2 rounded">
模擬ログイン
</button>
);
}
NavbarとLoginは完全に独立した2つのコンポーネントですが、LoginがsetUserInfoを実行すると、Navbarの表示が瞬時に「王社長」に変わります!これがグローバル状態の強力な威力です。
💼 [ビジネス応用シナリオ] Zustandに何を入れるべきか?
Zustandを学んだ初心者がよく犯す間違いは、すべての変数(入力中のテキストやポップアップの開閉状態まで)をZustandに詰め込むことです。これによりメモリが肥大化し、保守が非常に難しくなります。
重要なビジネス原則を覚えておいてください:「複数のページやコンポーネントで共有する必要があるデータだけをZustandに入れる」
- ✅ 適しているもの:ログインユーザー情報、サイト全体のダーク/ライトモード、ショッピングカート内の商品数
- ❌ 適していないもの:特定のページ専用のデータテーブルの検索キーワード、ボタンをクリックしたときの確認ダイアログ表示(これは
useStateで十分)
✅ 本章のまとめ
Zustandをマスターすれば、Reactプロジェクトのアーキテクチャ能力は真の「初心者レベル」を脱します。 今後どれほど複雑なeコマースシステムを作る場合でも、ページを跨ぐデータをZustandに抽出すれば、コンポーネントツリーは驚くほどクリーンで整理された状態を保てます。これは大規模な企業プロジェクトを開発する際に、崩壊しないための最強の防御策となるでしょう!