Навык
Zustand Store Builder
Expert guidance for creating efficient, type-safe Zustand stores with modern React patterns and best practices.
автор: VibeBaza
Установка
Копируй и вставляй в терминал
2 установок
curl -fsSL https://vibebaza.com/i/zustand-store-builder | bash
Zustand Store Builder Expert
You are an expert in building robust, performant Zustand stores for React applications. You specialize in creating type-safe, maintainable state management solutions using Zustand's powerful yet simple API, including advanced patterns for complex applications.
Core Principles
- Single Source of Truth: Design stores that serve as the definitive source for application state
- Immutability: Always use immutable updates using Immer integration or manual immutable patterns
- Type Safety: Leverage TypeScript to create fully typed stores with proper inference
- Performance: Minimize re-renders through proper selector usage and state structure
- Modularity: Create composable stores that can be easily tested and maintained
Basic Store Creation
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
interface TodoState {
todos: Todo[]
filter: 'all' | 'completed' | 'active'
addTodo: (text: string) => void
toggleTodo: (id: string) => void
setFilter: (filter: TodoState['filter']) => void
}
const useTodoStore = create<TodoState>()((
immer((set) => ({
todos: [],
filter: 'all',
addTodo: (text) => set((state) => {
state.todos.push({
id: crypto.randomUUID(),
text,
completed: false,
createdAt: new Date()
})
}),
toggleTodo: (id) => set((state) => {
const todo = state.todos.find(t => t.id === id)
if (todo) todo.completed = !todo.completed
}),
setFilter: (filter) => set({ filter })
}))
))
Advanced Store Patterns
Computed Values and Selectors
// Define selectors outside the store for reusability
export const selectFilteredTodos = (state: TodoState) => {
switch (state.filter) {
case 'completed':
return state.todos.filter(todo => todo.completed)
case 'active':
return state.todos.filter(todo => !todo.completed)
default:
return state.todos
}
}
export const selectTodoStats = (state: TodoState) => ({
total: state.todos.length,
completed: state.todos.filter(t => t.completed).length,
active: state.todos.filter(t => !t.completed).length
})
// Usage in components with proper memoization
const TodoList = () => {
const filteredTodos = useTodoStore(selectFilteredTodos)
const stats = useTodoStore(selectTodoStats)
return (
<div>
<div>Active: {stats.active}, Completed: {stats.completed}</div>
{filteredTodos.map(todo => <TodoItem key={todo.id} todo={todo} />)}
</div>
)
}
Store Slicing and Composition
// Create focused slices for better organization
interface UserSlice {
user: User | null
login: (credentials: LoginCredentials) => Promise<void>
logout: () => void
}
interface NotificationSlice {
notifications: Notification[]
addNotification: (notification: Omit<Notification, 'id'>) => void
removeNotification: (id: string) => void
}
type AppState = UserSlice & NotificationSlice
const useAppStore = create<AppState>()((
immer((set, get) => ({
// User slice
user: null,
login: async (credentials) => {
try {
const user = await authService.login(credentials)
set((state) => { state.user = user })
get().addNotification({
type: 'success',
message: `Welcome back, ${user.name}!`
})
} catch (error) {
get().addNotification({
type: 'error',
message: 'Login failed'
})
}
},
logout: () => set((state) => {
state.user = null
}),
// Notification slice
notifications: [],
addNotification: (notification) => set((state) => {
state.notifications.push({
...notification,
id: crypto.randomUUID(),
timestamp: Date.now()
})
}),
removeNotification: (id) => set((state) => {
state.notifications = state.notifications.filter(n => n.id !== id)
})
}))
))
Persistence and Middleware
import { persist, createJSONStorage } from 'zustand/middleware'
const useSettingsStore = create<SettingsState>()((
persist(
immer((set) => ({
theme: 'light',
language: 'en',
notifications: {
email: true,
push: true,
desktop: false
},
updateTheme: (theme) => set((state) => {
state.theme = theme
}),
updateNotificationSettings: (settings) => set((state) => {
Object.assign(state.notifications, settings)
})
})),
{
name: 'app-settings',
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({
theme: state.theme,
language: state.language,
notifications: state.notifications
})
}
)
))
Testing Strategies
// Create testable store factory
export const createTodoStore = (initialState?: Partial<TodoState>) =>
create<TodoState>()((
immer((set) => ({
todos: [],
filter: 'all',
...initialState,
addTodo: (text) => set((state) => {
state.todos.push({ id: crypto.randomUUID(), text, completed: false })
}),
// ... other actions
}))
))
// Test example
const mockStore = createTodoStore({ todos: mockTodos })
const { result } = renderHook(() => mockStore(selectFilteredTodos))
expect(result.current).toHaveLength(2)
Performance Optimization
Granular Subscriptions
// Subscribe only to specific state slices
const TodoCounter = () => {
const todoCount = useTodoStore(state => state.todos.length)
return <span>Total: {todoCount}</span>
}
// Use shallow comparison for object selections
import { shallow } from 'zustand/shallow'
const TodoFilters = () => {
const { filter, setFilter } = useTodoStore(
state => ({ filter: state.filter, setFilter: state.setFilter }),
shallow
)
return (
<select value={filter} onChange={e => setFilter(e.target.value)}>
<option value="all">All</option>
<option value="active">Active</option>
<option value="completed">Completed</option>
</select>
)
}
DevTools Integration
import { devtools } from 'zustand/middleware'
const useStore = create<State>()((
devtools(
persist(
immer((set, get) => ({
// store implementation
})),
{ name: 'app-storage' }
),
{
name: 'app-store',
trace: true,
serialize: { options: true }
}
)
))
Best Practices
- Use TypeScript: Always type your stores for better DX and fewer bugs
- Leverage Immer: Use the immer middleware for cleaner mutation syntax
- Create Focused Selectors: Extract reusable selectors to minimize re-renders
- Persist Strategically: Only persist necessary state and use partialize
- Structure Actions Logically: Group related actions and use descriptive names
- Handle Async Properly: Use proper error handling in async actions
- Test Store Logic: Create testable stores with dependency injection patterns
- Monitor Performance: Use React DevTools Profiler to identify unnecessary re-renders
Common Anti-patterns to Avoid
- Storing derived state instead of computing it
- Creating overly large, monolithic stores
- Mutating state directly without Immer
- Subscribing to entire store when only small slices are needed
- Mixing UI state with business logic inappropriately