前回、ことりずかんのデータをContextに移しました。Contextは標準の機能なのでまずこれで足ります。
ただ、実務のプロジェクトでは状態管理ライブラリが入っていることがよくあります。この回は「なぜ入るのか」を先に見てから、いちばん書きやすいZustandを入れます。
Contextで足りなくなるところ
Contextの弱点は1つだけです。値が1つでも変わると、そのContextを使っている部品が全部描き直されることです。
// この中に「鳥の一覧」「お気に入り」「検索語」を全部入れると……
<BirdsContext value={{ birds, favoriteIds, keyword }}>検索欄に1文字打つ→keywordが変わる→鳥の一覧しか使っていないカードまで描き直される。この仕組みは、次の章のレンダリングの回で詳しく見ます。
回避する手はあります。Contextを役割ごとに分ける、useMemoで値を固定する。ただ手当てが増えてくると、それはもうライブラリがやっていることの自前実装です。
| Contextのつらさ | 状態管理ライブラリの答え |
|---|---|
| 値が変わると使っている部品が全部描き直される | 必要な値だけ購読する(セレクタ) |
| Providerで包む階層が深くなる | Providerが要らない |
| 保存(localStorage)を自分で書く | 設定1つ(persist) |
| Reactの外から値を読めない | ストアはReactの外にあるのでどこからでも読める |
Zustandを入れる
npm install --save-exact zustandお気に入りをZustandのストアに移してみます。
import { create } from 'zustand'
export const useFavoriteStore = create((set) => ({
favoriteIds: [],
toggleFavorite: (id) =>
set((state) => ({
favoriteIds: state.favoriteIds.includes(id)
? state.favoriteIds.filter((favoriteId) => favoriteId !== id)
: [...state.favoriteIds, id],
})),
}))stateと、それを変える関数を同じ場所に書く——これがストアです。ContextのときのようにuseStateとProviderに分かれません。
使う側はこうです。
const favoriteIds = useFavoriteStore((state) => state.favoriteIds)
const toggleFavorite = useFavoriteStore((state) => state.toggleFavorite)Providerがありません。main.jsxを触る必要もありません。ストアはReactの外にあり、コンポーネントは「この値を見せて」と申し込むだけです。
保存を1行で足す
localStorageへの保存も、persistで包むだけです。custom-hookの回で自分で書いた読み書きが、まるごと消えます。
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
export const useFavoriteStore = create(
persist(
(set) => ({
favoriteIds: [],
toggleFavorite: (id) => set(/* 省略 */),
}),
{ name: 'zukan-favorites-store' }, // localStorageのキー名
),
)星を1つ押してから開発者ツールのApplication → Local Storageを見ると、こう入っています。
{"state":{"favoriteIds":[4]},"version":0}versionが付いているのは、あとで保存の形を変えたときに古いデータを変換できるようにするためです(migrateという設定があります)。自前のlocalStorage処理でいちばん忘れられるのがこれです。
どれを入れるか
名前をよく見るものだけ並べます。
| ライブラリ | 特徴 | 出会う場面 |
|---|---|---|
| Zustand | ストア1つ・Provider不要・学習量が少ない | 新規で入れるなら第一候補 |
| Jotai | 小さな状態(アトム)を組み合わせる | 状態が細かく分かれる画面 |
| Redux Toolkit | 決まりごとが多く、大人数でも崩れにくい | 既存の案件で現役。読めると強い |
| Context(標準) | 追加ゼロ。テーマ・ログイン中のユーザーなどめったに変わらない値 | まずここから |
Reactの外から読める、という利点
ストアがReactの外にあるので、コンポーネントでない場所からも読み書きできます。
// 通信処理の中など、フックが使えない場所から
const token = useAuthStore.getState().token
useAuthStore.getState().logout()getState()は購読しません(描き直しのきっかけになりません)。fetchの共通処理からログアウトさせたいときなどに効きます。あとのログインの回で実際に使います。
公式ドキュメントも見てみる
zustand.docs.pmnd.rs外部サイトIntroduction - ZustandZustand公式の導入。createの形と、セレクタで購読するという考え方が最初に出てきます(英語)このレッスンのまとめ
- Contextの弱点は値が1つ変わると使っている部品が全部描き直されること
- 状態管理ライブラリの答えはセレクタで必要な値だけ購読すること
- ZustandはProvider不要。ストアはReactの外にあり、コンポーネントは購読するだけ
useFavoriteStore((state) => state.favoriteIds)のカッコの中がセレクタ。全部取ると意味がなくなるpersistでlocalStorage保存が1行。保存する範囲はpartializeで絞る- サーバーのデータは入れない。それはTanStack Queryの担当
- 新規ならZustand/Jotai、既存案件ではRedux Toolkitが現役
次は、仕事で使うための章です。まずは「Reactはいつ描き直すのか」から。