中学生でもわかるZustand入門 - Reactの「データの共有ノート」をやさしく解説

この記事は何のための記事?
「Zustand(ズースタンド)」というものについて、プログラミングを始めたばかりの人でも分かるように、たとえ話を使って説明します。React(リアクト)というWebサイトやアプリを作るための道具を使っている人向けの話です。
そもそも「状態(state)」って何?
アプリの中には、「今どうなっているか」を覚えておく必要がある情報がたくさんあります。たとえばゲームアプリなら、
- 今の得点は何点か
- 今何面(ステージ)にいるか
- プレイヤーの名前は何か
こういう「今アプリが覚えている情報」のことを、プログラミングでは**「状態(state)」**と呼びます。得点が変わったら画面に表示される数字も変わりますよね。それは「状態が変わったから、画面もそれに合わせて書き換わった」ということです。
困りごと: 遠くの部品にまで情報を届けたい
Reactでは、画面の「ボタン」「文字」「入力欄」など一つ一つの部品をコンポーネントと呼びます。アプリは、たくさんのコンポーネントが親子関係でつながって出来ています。
アプリ全体
├── ヘッダー
│ └── ログインボタン
├── メインエリア
│ └── ショッピングカートのアイコン
└── フッター
もし「ログインした人の名前」を、離れた場所にある「ショッピングカートのアイコン」でも表示したい場合、普通のReactでは、情報を親から子へバケツリレーのように渡していく必要があります。関係のない中間のコンポーネントにも、「その情報を右から左へ受け渡すだけ」の作業をさせなければいけません。
これは、クラスで一番前の席の人が聞いた先生の話を、後ろの席の人に伝えたいときに、間の席の人全員に「そのまま後ろに伝えてね」と頼むようなものです。関係ない人まで巻き込まれて、面倒ですよね。
Zustandは「クラス全員が見られる黒板」
Zustandを使うと、この面倒さから解放されます。
Zustandは、たとえるなら教室の黒板のようなものです。
- 黒板に書かれた内容は、教室にいる誰でも見ることができる(バケツリレーで伝える必要がない)
- 誰でも黒板に書き込むことができる
- 黒板の内容が変わったら、それを見ている人はすぐに気づく
つまり「ログインした人の名前」を黒板(Zustandの保管場所。これを**ストア(store)**と呼びます)に書いておけば、ヘッダーだろうがショッピングカートだろうが、見たい人が直接黒板を見に行けばよいのです。間の人に伝言をお願いする必要がありません。
実際にコードを書いてみる: 得点カウンター
言葉だけだと分かりにくいので、実際に「得点を増やしたり減らしたりするアプリ」を作ってみましょう。
まず、黒板(ストア)を用意します。
import { create } from "zustand";
// 「score」という名前の黒板を作る
const useScoreStore = create((set) => ({
score: 0, // 最初は0点
// 「1点増やす」というルールを黒板に書いておく
addPoint: () => set((state) => ({ score: state.score + 1 })),
// 「0点に戻す」というルールも書いておく
reset: () => set({ score: 0 }),
}));
1行ずつ説明します。
create(...)は「新しい黒板を作りますよ」という意味ですscore: 0は、黒板の左上に「得点: 0」と書いてあるイメージですaddPointやresetは、「黒板をこう書き換えてね」というボタンのようなものです。この中で使っているsetが、実際に黒板の内容を書き換える係です
次に、この黒板を実際に見に行くコンポーネント(画面の部品)を作ります。
function ScoreBoard() {
// 黒板から「score」の値だけを見に行く
const score = useScoreStore((state) => state.score);
// 黒板から「addPoint」という書き換えボタンをもらう
const addPoint = useScoreStore((state) => state.addPoint);
return (
<div>
<p>今の得点: {score}</p>
<button onClick={addPoint}>1点追加!</button>
</div>
);
}
useScoreStore(...) の部分が「黒板を見に行く」動作です。カッコの中に (state) => state.score と書くことで、「黒板の中の、scoreっていう部分だけ教えて」とお願いしています。これをセレクターと呼びます。
このScoreBoardコンポーネントを、ヘッダーに置いてもフッターに置いても、同じ黒板(useScoreStore)を見ているので、どちらも同じ得点が表示されます。片方でボタンを押して得点を増やせば、もう片方の表示もちゃんと変わります。
なぜ「見たい部分だけ」お願いするの?
さっきのコードで、こう書いていました。
const score = useScoreStore((state) => state.score);
これを、こう書くこともできます。
const state = useScoreStore(); // 黒板全部をまるごと見る
でも、これはあまりおすすめできません。理由はこうです。
黒板には「得点」以外にも、いろいろな情報が書かれているとします(例えば「プレイヤーの名前」なども)。もし黒板を「まるごと」見ている場合、プレイヤーの名前がちょっと変わっただけでも、Reactは「もしかしたら画面の見た目を変える必要があるかも」と思って、そのコンポーネントを最初から書き直そう(これを再レンダリングと呼びます)とします。得点の表示には関係ないのに、です。
これは、黒板の隅っこにある「今日の給食メニュー」が書き換わるたびに、得点しか見ていない人まで「あ、黒板変わった!確認しなきゃ」と毎回振り向いてしまうようなものです。無駄ですよね。
なので、「自分が本当に必要な部分だけを教えて」とセレクターでお願いすることで、関係ない書き換えでは反応しないようにできます。これがZustandを使う上で一番大事なコツです。
サーバーからデータをもらう場合(非同期処理)
アプリによっては、自分のスマホやパソコンの中だけでなく、インターネットの向こう側にあるサーバーという別のコンピューターに「データちょうだい」とお願いして、返事を待つことがあります。友達にLINEで「今日の宿題何だっけ?」と聞いて、返事が来るまで少し待つのと似ています。
Zustandでは、これも特別な準備なしに、黒板のルールの中に書くことができます。
const useTodoStore = create((set) => ({
todos: [], // やることリスト(最初は空っぽ)
isLoading: false, // 今読み込み中かどうか
fetchTodos: async () => {
set({ isLoading: true }); // 「読み込み中」と黒板に書く
const response = await fetch("/api/todos"); // サーバーにお願いして、返事を待つ
const todos = await response.json(); // 返事の中身を取り出す
set({ todos: todos, isLoading: false }); // 黒板に「もらったリスト」を書いて、読み込み中は終わりにする
},
}));
await というのは「ここで返事が来るまで待つよ」という意味の言葉です。返事が来たら、次の行に進みます。
Zustandを使わないとどうなる?(比較)
Reactには他にも似たような道具(Context APIやRedux)がありますが、Zustandは特に「準備が少なくて済む」のが特徴です。
Zustand | Reactの標準機能(Context) | |
|---|---|---|
黒板を使うための下準備 | ほぼ不要 | アプリ全体を専用の枠で囲む必要がある |
書き方の量 | 少ない | やや多い |
関係ない書き換えで無駄に反応する | セレクターを使えば防げる | 気をつけないと防ぎにくい |
つまり「とりあえず簡単にアプリ全体で共有できるデータを作りたい」というときに、Zustandはとても手軽な選択肢です。
まとめ
- **状態(state)**とは、アプリが今覚えている情報のこと(得点、名前など)
- Zustandは、教室の黒板のように、アプリ中のどこからでも見たり書いたりできる共有の保管場所(ストア)を作れる道具
- 黒板を見に行くときは、**「自分が必要な部分だけ」**を指定する(セレクター)と、無駄な書き直しを防げる
- サーバーからデータをもらうような処理も、特別な準備なしにそのまま書ける
一度自分の手で小さな黒板(ストア)を1つ作ってみると、感覚がつかみやすいと思います。まずは今回の「得点カウンター」を真似して動かしてみるのがおすすめです。


