概要
Zustand の store をセレクタなしで参照すると、store 内の任意のフィールドが変わるたびにコンポーネントが再レンダリングされる。 セレクタ関数で参照するフィールドを絞ることで、そのフィールドが変わった場合のみ再レンダリングされる。
インストール
npm install zustand
実装例
// src/store/cartStore.ts
import { create } from "zustand";
type CartItem = { id: number; name: string; price: number };
type CartStore = {
items: CartItem[];
isOpen: boolean;
addItem: (item: CartItem) => void;
toggleCart: () => void;
};
export const useCartStore = create<CartStore>((set) => ({
items: [],
isOpen: false,
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
toggleCart: () => set((state) => ({ isOpen: !state.isOpen })),
}));
// src/components/CartBadge.tsx — items.length だけ参照(items が変わったときのみ再レンダリング)
"use client";
import { useCartStore } from "@/store/cartStore";
export function CartBadge() {
const itemCount = useCartStore((state) => state.items.length);
return (
<div className="relative">
<span className="text-sm">カート</span>
{itemCount > 0 && (
<span className="absolute -right-2 -top-2 flex h-4 w-4 items-center justify-center rounded-full bg-red-500 text-xs text-white">
{itemCount}
</span>
)}
</div>
);
}
// src/components/CartDrawer.tsx — 複数フィールドを取得する場合は useShallow
"use client";
import { useCartStore } from "@/store/cartStore";
import { useShallow } from "zustand/react/shallow";
export function CartDrawer() {
// オブジェクトとして複数フィールドを取得する場合は useShallow で shallow 比較
const { items, isOpen } = useCartStore(
useShallow((state) => ({ items: state.items, isOpen: state.isOpen }))
);
if (!isOpen) return null;
return (
<div className="fixed right-0 top-0 h-full w-72 border-l bg-white p-4 shadow-lg">
<h2 className="mb-4 font-bold">カート</h2>
{items.length === 0 ? (
<p className="text-sm text-gray-400">アイテムがありません</p>
) : (
<ul className="space-y-2">
{items.map((item) => (
<li key={item.id} className="flex justify-between text-sm">
<span>{item.name}</span>
<span>¥{item.price}</span>
</li>
))}
</ul>
)}
</div>
);
}
ポイント
useCartStore((state) => state.items.length)のように primitive を返すセレクタは参照等価で比較される- 複数フィールドをオブジェクトとして返すセレクタは
useShallowでラップして shallow 比較にする - アクション(
toggleCart等)は store 生成時から参照が変わらないため、セレクタなしで取得しても安全 - セレクタを使わず
useCartStore()全体を参照すると、isOpenだけ変わっても全コンポーネントが再レンダリングされる