These examples use Zustand 5. The Hook returned by create does not accept a second custom equality argument; use useShallow to stabilize object selector results.
Zustand is a lightweight, boilerplate-free React state management library.It is simpler than Redux, more efficient than Context, and is very suitable for small to medium-sized projects.
1. Zustand core concept
✅ Design philosophy
- Minimalist API
- Based on Hooks
- No Provider (default)
- Support middleware
- Support TS friendly
Core idea:
Use a global store + hook to selectively subscribe to state
2. Basic introduction
1️⃣ Installation
bash
npm install zustand@52️⃣ Create the first Store
ts
import { create } from 'zustand'
const useCounterStore = create((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
decrease: () => set((state) => ({ count: state.count - 1 })),
}))3️⃣ Used in components
tsx
function Counter() {
const { count, increase, decrease } = useCounterStore()
return (
<>
<div>{count}</div>
<button onClick={increase}>+</button>
<button onClick={decrease}>-</button>
</>
)
}- ✔ Automatic subscription
- ✔ Automatically unsubscribe when components are uninstalled
- ✔ No Provider
3. Advanced core skills
🎯 1. Accurate subscription (avoiding invalid rendering)
ts
const count = useCounterStore((state) => state.count)Only listens to count, changes in other fields will not trigger re-rendering.
Matching and comparison
ts
import { useShallow } from 'zustand/react/shallow'
const { count, increase } = useCounterStore(
useShallow((state) => ({ count: state.count, increase: state.increase }))
)🎯 2. Asynchronous operation (request interface)
ts
const useUserStore = create((set) => ({
user: null,
loading: false,
fetchUser: async () => {
set({ loading: true })
const res = await fetch('/api/user')
const data = await res.json()
set({ user: data, loading: false })
}
}))- ✔ Zustand does not limit async
- ✔ No thunk / saga concept
🎯 3. Middleware usage
persist (local storage)
ts
import { persist } from 'zustand/middleware'
const useStore = create(
persist(
(set) => ({
theme: 'light',
toggle: () => set((s) => ({ theme: s.theme === 'light' ? 'dark' : 'light' })),
}),
{
name: 'app-storage',
}
)
)devtools (debugging)
ts
import { devtools } from 'zustand/middleware'
const useStore = create(
devtools((set) => ({
count: 0,
inc: () => set((s) => ({ count: s.count + 1 }), false, 'inc')
}))
)Can be viewed in Redux DevTools.
🎯 4. Split modules (large projects)
Recommended structure:
store/
├── index.ts
├── user.ts
├── cart.ts
└── theme.tsModule merge
ts
import { create } from 'zustand'
import { createUserSlice } from './user'
import { createCartSlice } from './cart'
export const useStore = create((...a) => ({
...createUserSlice(...a),
...createCartSlice(...a),
}))- ✔ Similar to Redux slice
- ✔ Clearer
- ✔ High maintainability
4. Advanced practical skills
🔥 1. get() reads the latest status
ts
const useStore = create((set, get) => ({
count: 0,
double: () => {
const current = get().count
set({ count: current * 2 })
}
}))🔥 2. subscribe monitoring (non-component environment)
ts
const unsubscribe = useStore.subscribe((state, previous) => {
if (state.count !== previous.count) {
console.log('count changed:', state.count)
}
})
// Call unsubscribe() when the listener is no longer needed.Applies to:
- Logging system
- websocket
- External module listening
🔥 3. immer middleware (complex object)
ts
// Install the optional dependency first: npm install immer
import { immer } from 'zustand/middleware/immer'
const useStore = create(
immer((set) => ({
user: { name: '', age: 0 },
updateName: (name: string) =>
set((state) => {
state.user.name = name
}),
}))
)- ✔ Variable writing
- ✔ Internally automatic immutable
🔥 4. Reset Store
ts
const initialState = { count: 0 }
const useStore = create((set) => ({
...initialState,
reset: () => set(initialState),
}))5. Summary of performance optimization
✅Must do
- Use selector
- Use shallow
- split store
- Avoid returning large objects directly
❌Avoid
- Deconstruction of the entire state
- Write complex UI states in the store
- Put all states in one store
6. Zustand vs Redux comparison
| Comparison | Zustand | Redux |
|---|---|---|
| Learning cost | Low | High |
| Template code | Less | More |
| Middleware | Support | Strong |
| Suitable projects | Small/medium/medium-large | Medium-large |
7. Enterprise-level best practices
1️⃣ Status layering
- UI state (local)
- Global shared state (Zustand)
- Server status (React Query)
Recommended combination:
- Zustand + React Query
- Zustand + Vite
- Zustand + Next.js
2️⃣ Don’t abuse global state
Judgment criteria:
- Sharing multiple components?
- Does it span pages?
- Is caching required?
Otherwise use useState.
8. Summary of common pitfalls
- ⚠ Initialized twice under StrictMode
- ⚠ persist needs to handle version upgrades
- ⚠ SSR needs to avoid state pollution
- ⚠ Pay attention to coverage issues with asynchronous concurrency
9. Examples of actual project structures
src/
├── store/
│ ├── authStore.ts
│ ├── productStore.ts
│ └── index.ts
├── hooks/
├── pages/
└── services/🔟 Ultimate summary
Zustand is suitable for:
- ✔ Pursue simplicity
- ✔ Don’t want to write reducer
- ✔ Don’t want to configure complex middleware
- ✔ Want the ultimate development experience
One sentence summary:
Small but beautiful, sufficient and powerful.