【React状態管理】zustandが簡単で書きやすい
コンテキストの課題
「Providerタワーが積み重なってきたな・・・」
「このままグローバルstate管理をコンテキストに一任するのはまずい・・・」
「コンテキストに一任するとこんなデメリットがあるよね」
コンテキストのデメリット💣
- 依存していないstate更新によるコンポーネントの不要な再レンダリング
- 不要な際レンダリングを防止するためのメモ化のコスト増加
- Provider同士の依存による開発者の負荷増進
「コンテキストは必要に応じて使うべきで、」
「グローバルstate管理をしたいんだったら状態管理ライブラリを使った方がいいよね」
状態管理ライブラリ
「調べるとたくさんあるな」
「Reduxが一番有名だけど、」
「最近はMetaが開発してるRecoilなるものもあるらしい」
「でもまだバージョン1に満たないからプロジェクトには提案しにくいな・・・」
「npm trendsでみるとmobxていうのもあるんだ」
「このライブラリは古から使われているんだな」
「クラス記法で特殊っぽいし一旦置いておこう」
「zustand
ってライブラリ、Githubのスター数もそこそこあるし、成熟してる感もある」
「日本人が開発に携わってるんだ!」
「マスコットのくまもなんかかわいいし使ってみよう」
zustandでstoreの作成
- storeの作成には
create
を使う -
create
はset
,get
を引数に取る -
set
はstateの更新 -
get
はstateの取得 - 非同期処理もstore内部で簡単に書ける
import create from 'zustand'
const useStore = create((set, get) => ({
editedTodo: {
id: 0,
title: '',
}
todos: [],
fetchTodos: async () => {
const res = await fetchTodos()
set({ todos: res.todos })
},
createTodo: () => {
const newTodo = {
...get().editedTodo,
id: get().todos.length + 1,
completed: false,
}
set((state) => ({ todos: [...state.todos, newTodo] }))
},
}))
storeの利用
- storeを丸ごと参照すると内部のstateの更新による再レンダリングが発生する
- 不要な再レンダリングを防ぐためにはstateごとの指定が必要
- stateの不要な再生成を防ぐためにuseCallbackでメモ化することが推奨されている
export const TodoList = () => {
// store丸ごと
const store = useStore()
// stateごと
const todos = useStore(state => state.todos)
const firstTodo = useStore(useCallback(state => state.todos[idx], [idx]))
return (
...
)
}
storeは複数作れる
Since you can create as many stores as you like, forwarding results to succeeding selectors is as natural as it gets.
ストアはいくつでも作ることができるので、結果を後続のセレクタに転送するのはごく自然なことです。(DeepL)
const currentBear = useCredentialsStore((state) => state.currentBear)
const bear = useBearStore((state) => state.bears[currentBear])
「Reduxみたいに非同期処理の扱い方が特殊じゃないし、」
「複数のファイルをまたがって構成されてないからわかりやすいな」
「コンテキストみたいにstateを個別参照しても不要な再レンダリングが起こることがないから安心して使える」
「ただ、レンダリングを抑えるためには個別にstateを参照する必要があるのと、」
「useCallbackでメモ化を考慮しなくちゃいけないのは注意が必要だ」
Discussion