BLOG

ブログ

2025/11/17 技術系

【React】状態管理ライブラリZustandを使ってみた

この記事を書いた人 Y.S

きっかけ

Reactで状態管理に困ったことありませんか?

例えばECサイトを作る場合、カートの状態をいろんなコンポーネントで使いたくなります。

  • ヘッダー:カート内の商品数を表示したい
  • 商品ページや商品一覧ページ:商品をカートに追加したい
  • カートページ:カートの中身を一覧表示したい
  • でも全部違う階層のコンポーネント…

こういう時、Propsを何階層にもわたって渡すのは大変です。そこでZustandという状態管理ライブラリを使ってみました。

Zustandとは…

一言でいうと、シンプルな状態管理ライブラリです。

下記のコマンドで簡単に導入できます。

npm install zustand

基本的な使い方

ECサイトのカート機能を例に説明します。

1. まずは”store”を作る

アプリケーション全体で共有したいデータ(状態)とそれを操作する”store”を作成します。

今回はuseCartStoreという”store”をcreate関数で作成することで、どのコンポーネントからでも呼び出すことができ、同じ状態を共有できるようになります。

// store/cart.js
import { create } from 'zustand'

const useCartStore = create((set) => ({
  // 初期状態
  items: [],
  
  // カートに商品を追加する関数
  addToCart: (item) => set((state) => ({
    items: [...state.items, item]  // 既存のitemsに新しいitemを追加
  })),
  
  // カートから商品を削除する関数
  removeItem: (id) => set((state) => ({
    items: state.items.filter(item => item.id !== id)  // 指定したid以外を残す
  })),
  
  // カートを空にする関数
  clearCart: () => set({ items: [] })  // itemsを空の配列にリセット
}))

export { useCartStore }
  • create():Zustandのストアを作成する関数
  • set():状態を更新するための関数
  • state:現在の状態を取得するためのパラメータ

各関数は状態を更新して新しいオブジェクトを返します。

2. storeの”状態”を取得・操作する

先程作成したZustandのstoreでは、複数のコンポーネント間で共有したい状態と、その状態を操作する関数が入っています。

作成したstore(useCartStore)を各コンポーネントでimportすることで、どこからでも同じ状態(state)を参照できるようになります。importした後は、通常のReact Hooksと同じようにコンポーネント内で呼び出して使用します。

// Header.jsx
import { useCartStore } from './store/cart'

function Header() {
  // カートのアイテム数だけを取得
  const itemCount = useCartStore(state => state.items.length)
  return <nav>カート: {itemCount}個</nav>
}

// ProductCard.jsx  
import { useCartStore } from './store/cart'

function ProductCard({ product }) {
  // addToCart関数だけを取得
  const addToCart = useCartStore(state => state.addToCart)
  return (
    <button onClick={() => addToCart(product)}>
      カートに追加
    </button>
  )
}

// CartPage.jsx
import { useCartStore } from './store/cart'

function CartPage() {
  // 複数の状態・関数を同時に取得
  const items = useCartStore(state => state.items)
  const clearCart = useCartStore(state => state.clearCart)
  
  return (
    <div>
      {items.map(item => <CartItem key={item.id} item={item} />)}
      <button onClick={clearCart}>カートを空にする</button>
    </div>
  )
}
  • useCartStore(state => state.xxx):セレクター関数で必要な部分だけを取得

便利だった点

Propsバケツリレーが不要

従来のReactだと

// App → Layout → Header → CartIconと
// 使わないコンポーネントも経由する必要があった
function Layout({ cartItems }) {  // Layoutは使わないけど受け取る
  return <Header cartItems={cartItems} />  // Headerに渡すだけの役割
}

Zustandの場合

// 使いたいコンポーネントで直接取得
function CartIcon() {
  const items = useCartStore(state => state.items)  // 直接ストアから取得
  return <span>{items.length}</span>
}
  • 従来の方法では中間コンポーネントが「propsの運び屋」になってしまう
  • Zustandなら必要なコンポーネントで直接データを取得できる

Zustandが向いてる場面・向いてない場面

  • 向いてる場面
    • グローバルな状態管理が必要(カート、認証、テーマ設定など)
    • 複数のページ/コンポーネントで同じデータを使う
  • 向いていない場面
    • 親子コンポーネント間だけの状態管理(useStateで十分な時)
    • フォームの状態管理

まとめ

はじめてZustandを使ってみて、普通のReact Hooksと同じような感覚でグローバル状態管理が簡単にできることに驚きました。

特に、Propsを何階層にわたって渡さなくて良くなり、「このデータ、どこから来てたんだっけ?」と確認する手間が省けて作業が効率化できるなと個人的には感じました。

今回の例のECサイトのような、いろんな場所で同じデータを使うアプリには最適だと思います。(例えばカート情報や、ユーザー情報など)

ご参考になりましたら幸いです。

参考リンク


株式会社ウイングドアは福岡のシステム開発会社です。
現在、私達と一緒に"楽しく仕事が出来る仲間"として、新卒・中途採用を絶賛募集しています!
ウイングドアの仲間達となら楽しく仕事できるかも?と興味をもった方、
お気軽にお問い合わせ下さい!

アーカイブ