Zustand
此内容尚不支持你的语言。
Zustand is a small, fast, and scalable bearbones state management solution. Zustand has a comfy API based on hooks. It isn’t boilerplatey or opinionated, but has enough convention to be explicit and flux-like.
Usage
First Create a Store
Your store is a hook! You can put anything in it: primitives, objects, functions. The set
function merges state.
import { create } from 'zustand'
const useStore = create((set) => ({ bears: 0, increasePopulation: () => set((state) => ({ bears: state.bears + 1 })), removeAllBears: () => set({ bears: 0 }), updateBears: (newBears) => set({ bears: newBears }),}))
Then Bind your Components, and that’s it!
You can use the hook anywhere, without the need of providers. Select your state and the consuming component will re-render when that state changes.
function BearCounter() { const bears = useStore((state) => state.bears) return <h1>{bears} around here...</h1>}
function Controls() { const increasePopulation = useStore((state) => state.increasePopulation) return <button onClick={increasePopulation}>one up</button>}
Comparison
Zustand is one of many state management libraries for React. On this page we will discuss Zustand in comparison to some of these libraries, including Redux, Valtio, Jotai, and Recoil.
Each library has its own strengths and weaknesses, and we will compare key differences and similarities between each.
Zustand vs Redux
State Model
Conceptually, Zustand and Redux are quite similar, both are based on an immutable state model. However, Redux requires your app to be wrapped in context providers; Zustand does not.
import { create } from 'zustand'
type State = { count: number}
type Actions = { increment: (qty: number) => void decrement: (qty: number) => void}
const useCountStore = create<State & Actions>((set) => ({ count: 0, increment: (qty: number) => set((state) => ({ count: state.count + qty })), decrement: (qty: number) => set((state) => ({ count: state.count - qty })),}))
import { create } from 'zustand'
type State = { count: number}
type Actions = { increment: (qty: number) => void decrement: (qty: number) => void}
type Action = { type: keyof Actions qty: number}
const countReducer = (state: State, action: Action) => { switch (action.type) { case 'increment': return { count: state.count + action.qty } case 'decrement': return { count: state.count - action.qty } default: return state }}
const useCountStore = create<State & Actions>((set) => ({ count: 0, dispatch: (action: Action) => set((state) => countReducer(state, action)),}))
import { createStore } from 'redux'import { useSelector, useDispatch } from 'react-redux'
type State = { count: number}
type Action = { type: 'increment' | 'decrement' qty: number}
const countReducer = (state: State, action: Action) => { switch (action.type) { case 'increment': return { count: state.count + action.qty } case 'decrement': return { count: state.count - action.qty } default: return state }}
const countStore = createStore(countReducer)
import { createSlice, configureStore } from '@reduxjs/toolkit'
const countSlice = createSlice({ name: 'count', initialState: { value: 0 }, reducers: { incremented: (state, qty: number) => { // Redux Toolkit does not mutate the state, it uses the Immer library // behind scenes, allowing us to have something called "draft state". state.value += qty }, decremented: (state, qty: number) => { state.value -= qty }, },})
const countStore = configureStore({ reducer: countSlice.reducer })
Render Optimization
When it comes to render optimizations within your app, there are no major differences in approach between Zustand and Redux. In both libraries it is recommended that you manually apply render optimizations by using selectors.
import { create } from 'zustand'
type State = { count: number}
type Actions = { increment: (qty: number) => void decrement: (qty: number) => void}
const useCountStore = create<State & Actions>((set) => ({ count: 0, increment: (qty: number) => set((state) => ({ count: state.count + qty })), decrement: (qty: number) => set((state) => ({ count: state.count - qty })),}))
const Component = () => { const count = useCountStore((state) => state.count) const increment = useCountStore((state) => state.increment) const decrement = useCountStore((state) => state.decrement) // ...}
import { createStore } from 'redux'import { useSelector, useDispatch } from 'react-redux'
type State = { count: number}
type Action = { type: 'increment' | 'decrement' qty: number}
const countReducer = (state: State, action: Action) => { switch (action.type) { case 'increment': return { count: state.count + action.qty } case 'decrement': return { count: state.count - action.qty } default: return state }}
const countStore = createStore(countReducer)
const Component = () => { const count = useSelector((state) => state.count) const dispatch = useDispatch() // ...}
import { useSelector } from 'react-redux'import type { TypedUseSelectorHook } from 'react-redux'import { createSlice, configureStore } from '@reduxjs/toolkit'
const countSlice = createSlice({ name: 'count', initialState: { value: 0 }, reducers: { incremented: (state, qty: number) => { // Redux Toolkit does not mutate the state, it uses the Immer library // behind scenes, allowing us to have something called "draft state". state.value += qty }, decremented: (state, qty: number) => { state.value -= qty }, },})
const countStore = configureStore({ reducer: countSlice.reducer })
const useAppSelector: TypedUseSelectorHook<typeof countStore.getState> = useSelector
const useAppDispatch: () => typeof countStore.dispatch = useDispatch
const Component = () => { const count = useAppSelector((state) => state.count.value) const dispatch = useAppDispatch() // ...}
Zustand vs Valtio
State Model
Zustand and Valtio approach state management in a fundamentally different way. Zustand is based on the immutable state model, while Valtio is based on the mutable state model.
import { create } from 'zustand'
type State = { obj: { count: number }}
const store = create<State>(() => ({ obj: { count: 0 } }))
store.setState((prev) => ({ obj: { count: prev.obj.count + 1 } }))
import { proxy } from 'valtio'
const state = proxy({ obj: { count: 0 } })
state.obj.count += 1
Render Optimization
The other difference between Zustand and Valtio is Valtio makes render optimizations through property access. However, with Zustand, it is recommended that you manually apply render optimizations by using selectors.
import { create } from 'zustand'
type State = { count: number}
const useCountStore = create<State>(() => ({ count: 0,}))
const Component = () => { const count = useCountStore((state) => state.count) // ...}
import { proxy, useSnapshot } from 'valtio'
const state = proxy({ count: 0,})
const Component = () => { const { count } = useSnapshot(state) // ...}
Zustand vs Jotai
State Model
There are two major differences between Zustand and Jotai. Firstly, Zustand is a single store, while Jotai consists of primitive atoms that can be composed together. Secondly, a Zustand store is an external store, making it more suitable when access outside of React is required.
import { create } from 'zustand'
type State = { count: number}
type Actions = { updateCount: ( countCallback: (count: State['count']) => State['count'], ) => void}
const useCountStore = create<State & Actions>((set) => ({ count: 0, updateCount: (countCallback) => set((state) => ({ count: countCallback(state.count) })),}))
import { atom } from 'jotai'
const countAtom = atom<number>(0)
Render Optimization
Jotai achieves render optimizations through atom dependency. However, with Zustand it is recommended that you manually apply render optimizations by using selectors.
import { create } from 'zustand'
type State = { count: number}
type Actions = { updateCount: ( countCallback: (count: State['count']) => State['count'], ) => void}
const useCountStore = create<State & Actions>((set) => ({ count: 0, updateCount: (countCallback) => set((state) => ({ count: countCallback(state.count) })),}))
const Component = () => { const count = useCountStore((state) => state.count) const updateCount = useCountStore((state) => state.updateCount) // ...}
import { atom, useAtom } from 'jotai'
const countAtom = atom<number>(0)
const Component = () => { const [count, updateCount] = useAtom(countAtom) // ...}
Zustand vs Recoil
State Model
The difference between Zustand and Recoil is similar to that between Zustand and Jotai. Recoil depends on atom string keys instead of atom object referential identities. Additionally, Recoil needs to wrap your app in a context provider.
import { create } from 'zustand'
type State = { count: number}
type Actions = { setCount: (countCallback: (count: State['count']) => State['count']) => void}
const useCountStore = create<State & Actions>((set) => ({ count: 0, setCount: (countCallback) => set((state) => ({ count: countCallback(state.count) })),}))
import { atom } from 'recoil'
const count = atom({ key: 'count', default: 0,})
Render Optimization
Similar to previous optimization comparisons, Recoil makes render optimizations through atom dependency. Whereas with Zustand, it is recommended that you manually apply render optimizations by using selectors.
import { create } from 'zustand'
type State = { count: number}
type Actions = { setCount: (countCallback: (count: State['count']) => State['count']) => void}
const useCountStore = create<State & Actions>((set) => ({ count: 0, setCount: (countCallback) => set((state) => ({ count: countCallback(state.count) })),}))
const Component = () => { const count = useCountStore((state) => state.count) const setCount = useCountStore((state) => state.setCount) // ...}
import { atom, useRecoilState } from 'recoil'
const countAtom = atom({ key: 'count', default: 0,})
const Component = () => { const [count, setCount] = useRecoilState(countAtom) // ...}