乐闻世界logo
搜索文章和话题

Zustand

Zustand 是一个简单、快速、可扩展的状态管理库,用于 React 和 React Native 应用程序。它提供了一种创建全局状态的简便方法,而无需过多地关注 Redux 或 Context API 的复杂性。Zustand 的核心概念是创建一个存储(store),其中包含了应用程序的状态和可变更该状态的函数。
Zustand
查看更多相关内容
如何在 Zustand 中优化状态更新和性能?### Zustand 中的性能优化方法: 1. **选择性订阅**: ```javascript // 不推荐:订阅整个 store,会导致组件在任何状态变化时都重渲染 const { count, user } = useStore(); // 推荐:只订阅需要的状态部分 const count = useStore((state) => state.count); const user = useStore((state) => state.user); ``` 2. **使用 shallow 比较**(对于复杂对象): ```javascript import { create } from 'zustand'; import { shallow } from 'zustand/shallow'; // 订阅多个状态并使用 shallow 比较 const { count, user } = useStore( (state) => ({ count: state.count, user: state.user }), shallow // 只有当 count 或 user 真正变化时才重渲染 ); ``` 3. **状态拆分**: ```javascript // 按功能拆分多个 store // userStore.js const useUserStore = create((set) => ({ user: null, setUser: (user) => set({ user }) })); // counterStore.js const useCounterStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })) })); ``` 4. **使用 get 访问当前状态**(避免闭包陷阱): ```javascript const useStore = create((set, get) => ({ count: 0, // 推荐:使用 get 获取最新状态 increment: () => set((state) => ({ count: state.count + 1 })), // 也可以使用 get incrementAsync: async () => { await someAsyncOperation(); set({ count: get().count + 1 }); } })); ``` 5. **批量更新**: ```javascript // Zustand 会自动批量处理多个 set 调用 const updateMultiple = () => { set({ count: 1 }); set({ user: { name: 'John' } }); // 只会触发一次重渲染 }; ``` 6. **避免在组件渲染时创建新函数**: ```javascript // 不推荐:每次渲染都创建新函数 const incrementBy = (value) => useStore.getState().incrementBy(value); // 推荐:在 store 中定义方法 // 在 store 中: incrementBy: (value) => set((state) => ({ count: state.count + value })) // 在组件中: const incrementBy = useStore((state) => state.incrementBy); ``` ### 关键点: * 选择性订阅是 Zustand 性能优化的核心 * 使用 shallow 比较可以优化复杂对象的订阅 * 状态拆分可以减少不必要的重渲染 * 合理使用 get 可以避免闭包陷阱 * Zustand 会自动处理批量更新
服务端 · 3月7日 12:01
如何在 Zustand 中创建自定义中间件?在 Zustand 中创建自定义中间件非常灵活,可以用来实现各种功能,如日志记录、状态验证、性能监控等。 ### 基本自定义中间件结构: ```javascript const customMiddleware = (config) => (set, get, api) => { // 在原始 store 之前执行的逻辑 const originalSet = set; // 包装 set 函数 const wrappedSet = (partial, replace) => { // 在状态更新前执行逻辑 console.log('State will update:', partial); // 调用原始 set const result = originalSet(partial, replace); // 在状态更新后执行逻辑 console.log('State updated:', get()); return result; }; // 创建 store const store = config(wrappedSet, get, api); // 返回增强后的 store return store; }; ``` ### 示例 1:日志中间件 ```javascript const loggerMiddleware = (config) => (set, get, api) => { const originalSet = set; const wrappedSet = (partial, replace) => { const previousState = get(); const result = originalSet(partial, replace); const nextState = get(); console.log('Previous state:', previousState); console.log('Action:', partial); console.log('Next state:', nextState); return result; }; return config(wrappedSet, get, api); }; // 使用 const useStore = create( loggerMiddleware((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })) })) ); ``` ### 示例 2:状态验证中间件 ```javascript const validationMiddleware = (schema) => (config) => (set, get, api) => { const originalSet = set; const wrappedSet = (partial, replace) => { // 验证状态更新 const newState = typeof partial === 'function' ? partial(get()) : partial; const validation = schema.safeParse({ ...get(), ...newState }); if (!validation.success) { console.error('State validation failed:', validation.error); throw new Error('Invalid state update'); } return originalSet(partial, replace); }; return config(wrappedSet, get, api); }; // 使用 import { z } from 'zod'; const storeSchema = z.object({ count: z.number().min(0), user: z.object({ id: z.string(), name: z.string().min(1) }).nullable() }); const useStore = create( validationMiddleware(storeSchema)((set) => ({ count: 0, user: null, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: Math.max(0, state.count - 1) })) })) ); ``` ### 示例 3:性能监控中间件 ```javascript const performanceMiddleware = (config) => (set, get, api) => { const originalSet = set; const renderCounts = {}; const wrappedSet = (partial, replace) => { const startTime = performance.now(); const result = originalSet(partial, replace); const endTime = performance.now(); const duration = endTime - startTime; if (duration > 10) { console.warn(`Slow state update: ${duration.toFixed(2)}ms`, partial); } return result; }; const store = config(wrappedSet, get, api); // 跟踪组件渲染次数 const originalSubscribe = api.subscribe; api.subscribe = (listener, selector) => { const wrappedListener = (state, previousState) => { const key = selector ? selector.toString() : 'full-store'; renderCounts[key] = (renderCounts[key] || 0) + 1; if (renderCounts[key] % 10 === 0) { console.log(`Render count for ${key}:`, renderCounts[key]); } listener(state, previousState); }; return originalSubscribe(wrappedListener, selector); }; return store; }; // 使用 const useStore = create( performanceMiddleware((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })) })) ); ``` ### 示例 4:撤销/重做中间件 ```javascript const undoRedoMiddleware = (config) => (set, get, api) => { let history = []; let future = []; const MAX_HISTORY = 50; const originalSet = set; const wrappedSet = (partial, replace) => { const previousState = get(); const result = originalSet(partial, replace); const nextState = get(); // 保存到历史记录 history.push(previousState); if (history.length > MAX_HISTORY) { history.shift(); } // 清空未来记录 future = []; return result; }; const store = config(wrappedSet, get, api); // 添加撤销功能 store.undo = () => { if (history.length === 0) return; const previousState = history.pop(); future.push(get()); originalSet(previousState, true); }; // 添加重做功能 store.redo = () => { if (future.length === 0) return; const nextState = future.pop(); history.push(get()); originalSet(nextState, true); }; // 清空历史 store.clearHistory = () => { history = []; future = []; }; return store; }; // 使用 const useStore = create( undoRedoMiddleware((set, get) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })) })) ); // 在组件中使用 function Counter() { const { count, increment, decrement } = useStore(); const undo = useStore((state) => state.undo); const redo = useStore((state) => state.redo); return ( <div> <h1>Count: {count}</h1> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> <button onClick={undo}>Undo</button> <button onClick={redo}>Redo</button> </div> ); } ``` ### 关键点: * 自定义中间件是一个高阶函数,接收 config 并返回新的配置函数 * 可以包装 set、get 和 api 来增强功能 * 中间件的执行顺序很重要,通常外层中间件先执行 * 可以在中间件中添加额外的功能,如日志、验证、性能监控等 * 中间件可以返回增强后的 store,添加新的方法或属性
服务端 · 3月7日 12:01
如何在 Zustand 中处理异步操作?### 在 Zustand 中处理异步操作的方法: 1. **基本异步操作**: ```javascript import { create } from 'zustand'; const useStore = create((set, get) => ({ // 状态 user: null, loading: false, error: null, // 异步操作 fetchUser: async (userId) => { try { set({ loading: true, error: null }); const response = await fetch(`/api/users/${userId}`); const userData = await response.json(); set({ user: userData, loading: false }); } catch (err) { set({ error: err.message, loading: false }); } }, // 另一种方式:使用 get 获取最新状态 updateUserProfile: async (updates) => { try { set({ loading: true, error: null }); const currentUser = get().user; const response = await fetch(`/api/users/${currentUser.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updates) }); const updatedUser = await response.json(); set({ user: updatedUser, loading: false }); } catch (err) { set({ error: err.message, loading: false }); } } })); ``` 2. **使用 Promise 链**: ```javascript const useStore = create((set) => ({ data: null, status: 'idle', // idle, loading, success, error fetchData: () => { set({ status: 'loading' }); return fetch('/api/data') .then((response) => response.json()) .then((data) => { set({ data, status: 'success' }); return data; }) .catch((error) => { set({ error: error.message, status: 'error' }); throw error; }); } })); ``` 3. **结合 React Query 或 SWR**: ```javascript // 可以在 Zustand 中存储查询结果,同时使用 React Query 处理缓存和失效 import { create } from 'zustand'; import { useQuery } from 'react-query'; const useStore = create((set) => ({ user: null, setUser: (user) => set({ user }) })); // 在组件中 function UserProfile({ userId }) { const { data, isLoading, error } = useQuery( ['user', userId], () => fetch(`/api/users/${userId}`).then(res => res.json()) ); // 当查询成功时,更新 Zustand store React.useEffect(() => { if (data) { useStore.getState().setUser(data); } }, [data]); // 使用 Zustand 中的用户数据 const user = useStore(state => state.user); return ( <div> {isLoading && <p>Loading...</p>} {error && <p>Error: {error.message}</p>} {user && <p>User: {user.name}</p>} </div> ); } ``` ### 关键点: * Zustand 支持直接在 store 方法中使用 async/await * 可以在异步操作中管理 loading 和 error 状态 * 使用 `get()` 获取最新状态,避免闭包陷阱 * 可以返回 Promise 以便在组件中处理异步操作的结果 * 可以与 React Query 或 SWR 等库结合使用,获得更好的缓存和失效策略
服务端 · 3月7日 11:48
Zustand 中级面试题:如何对 Zustand store 进行单元测试?对 Zustand store 进行单元测试相对简单,因为 store 是纯 JavaScript 对象。 ### 基本测试示例: ```javascript // store.js import { create } from 'zustand'; const useStore = create((set) => ({ count: 0, user: null, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })), setUser: (user) => set({ user }), reset: () => set({ count: 0, user: null }) })); export default useStore; ``` ```javascript // store.test.js import { renderHook, act } from '@testing-library/react'; import useStore from './store'; describe('Zustand Store', () => { beforeEach(() => { // 每个测试前重置 store useStore.setState({ count: 0, user: null }); }); test('should initialize with default values', () => { const { result } = renderHook(() => useStore()); expect(result.current.count).toBe(0); expect(result.current.user).toBeNull(); }); test('should increment count', () => { const { result } = renderHook(() => useStore()); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); }); test('should decrement count', () => { const { result } = renderHook(() => useStore()); act(() => { result.current.decrement(); }); expect(result.current.count).toBe(-1); }); test('should set user', () => { const { result } = renderHook(() => useStore()); const mockUser = { id: 1, name: 'John' }; act(() => { result.current.setUser(mockUser); }); expect(result.current.user).toEqual(mockUser); }); test('should reset store', () => { const { result } = renderHook(() => useStore()); const mockUser = { id: 1, name: 'John' }; act(() => { result.current.setUser(mockUser); result.current.increment(); }); expect(result.current.count).toBe(1); expect(result.current.user).toEqual(mockUser); act(() => { result.current.reset(); }); expect(result.current.count).toBe(0); expect(result.current.user).toBeNull(); }); }); ``` ### 测试异步操作: ```javascript // store.js const useStore = create((set) => ({ user: null, loading: false, error: null, fetchUser: async (userId) => { try { set({ loading: true, error: null }); const response = await fetch(`/api/users/${userId}`); const userData = await response.json(); set({ user: userData, loading: false }); } catch (err) { set({ error: err.message, loading: false }); } } })); ``` ```javascript // store.test.js import { renderHook, act, waitFor } from '@testing-library/react'; import useStore from './store'; describe('Zustand Store - Async Operations', () => { beforeEach(() => { useStore.setState({ user: null, loading: false, error: null }); }); test('should fetch user successfully', async () => { global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve({ id: 1, name: 'John' }) }) ); const { result } = renderHook(() => useStore()); await act(async () => { await result.current.fetchUser(1); }); expect(result.current.user).toEqual({ id: 1, name: 'John' }); expect(result.current.loading).toBe(false); expect(result.current.error).toBeNull(); }); test('should handle fetch error', async () => { global.fetch = jest.fn(() => Promise.reject(new Error('Network error'))); const { result } = renderHook(() => useStore()); await act(async () => { await result.current.fetchUser(1); }); expect(result.current.error).toBe('Network error'); expect(result.current.loading).toBe(false); }); }); ``` ### 测试选择性订阅: ```javascript import { renderHook } from '@testing-library/react'; import useStore from './store'; describe('Zustand Store - Selective Subscription', () => { beforeEach(() => { useStore.setState({ count: 0, user: null }); }); test('should only re-render when subscribed state changes', () => { const renderCount = jest.fn(); const { result } = renderHook(() => { renderCount(); return useStore((state) => state.count); }); expect(renderCount).toHaveBeenCalledTimes(1); act(() => { useStore.getState().setUser({ id: 1, name: 'John' }); }); // 不应该重新渲染,因为 user 变化,但订阅的是 count expect(renderCount).toHaveBeenCalledTimes(1); act(() => { useStore.getState().increment(); }); // 应该重新渲染,因为 count 变化 expect(renderCount).toHaveBeenCalledTimes(2); }); }); ``` ### 关键点: * 使用 `@testing-library/react` 的 `renderHook` 和 `act` 进行测试 * 在每个测试前重置 store 状态 * 对于异步操作,使用 `waitFor` 等待状态更新 * 测试选择性订阅时,验证重渲染次数 * 使用 `useStore.getState()` 直接访问和操作 store
服务端 · 3月7日 11:44
如何在 React Native 中使用 Zustand 管理状态?### React Native 中使用 Zustand 的方法: 1. **安装 Zustand** ```bash npm install zustand # 或 yarn add zustand ``` 2. **创建 Store** ```javascript import { create } from 'zustand'; const useCounterStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })), })); ``` 3. **在 React Native 组件中使用** ```javascript import React from 'react'; import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; import { useCounterStore } from './store'; export default function CounterScreen() { const count = useCounterStore((state) => state.count); const increment = useCounterStore((state) => state.increment); const decrement = useCounterStore((state) => state.decrement); return ( <View style={styles.container}> <Text style={styles.count}>{count}</Text> <View style={styles.buttons}> <TouchableOpacity style={styles.button} onPress={decrement}> <Text style={styles.buttonText}>-</Text> </TouchableOpacity> <TouchableOpacity style={styles.button} onPress={increment}> <Text style={styles.buttonText}>+</Text> </TouchableOpacity> </View> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, count: { fontSize: 48, marginBottom: 20, }, buttons: { flexDirection: 'row', gap: 20, }, button: { backgroundColor: '#007AFF', padding: 15, borderRadius: 8, }, buttonText: { color: 'white', fontSize: 24, fontWeight: 'bold', }, }); ``` 4. **持久化状态** - 使用 persist 中间件保存状态到 AsyncStorage - 示例: ```javascript import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import AsyncStorage from '@react-native-async-storage/async-storage'; const useUserStore = create( persist( (set) => ({ user: null, setUser: (user) => set({ user }), }), { name: 'user-storage', storage: { getItem: async (name) => { const item = await AsyncStorage.getItem(name); return item ? JSON.parse(item) : null; }, setItem: async (name, value) => { await AsyncStorage.setItem(name, JSON.stringify(value)); }, removeItem: async (name) => { await AsyncStorage.removeItem(name); }, }, } ) ); ``` 5. **React Native 特定优化** - 避免在选择器中使用复杂计算 - 合理使用 useCallback 缓存回调函数 - 注意 AsyncStorage 的性能影响 6. **常见使用场景** - 用户认证状态管理 - 应用设置和偏好 - 购物车状态 - 导航状态管理
服务端 · 3月7日 11:44
Zustand 与 Redux 相比有哪些主要区别?### Zustand 与 Redux 的核心区别: 1. **API 复杂度** - **Zustand**:简洁的函数式 API,无需 action types、reducers、dispatch 等概念 - **Redux**:需要定义 action types、reducers、使用 dispatch 分发 actions 2. **代码量** - **Zustand**:最小化样板代码,几行代码即可创建 store - **Redux**:需要更多的样板代码,包括 actions、reducers、store 配置等 3. **Provider 需求** - **Zustand**:无需 Provider 组件,直接使用 hook - **Redux**:需要在应用顶层包裹 Provider 组件 4. **状态更新** - **Zustand**:直接使用 set 函数更新状态,支持函数式更新 - **Redux**:通过 dispatch actions,由 reducers 处理状态更新 5. **中间件** - **Zustand**:内置支持中间件,如 persist、devtools 等 - **Redux**:需要单独安装中间件,如 redux-thunk、redux-saga 等 6. **性能优化** - **Zustand**:自动优化重渲染,只订阅需要的状态 - **Redux**:需要手动使用 selectors 优化性能 7. **类型安全** - **Zustand**:良好的 TypeScript 支持,类型推断更简单 - **Redux**:需要更多的类型定义,如 action types、reducer types 等 8. **适用场景** - **Zustand**:适合中小型应用,快速开发 - **Redux**:适合大型应用,需要严格的状态管理规范 ### 选择建议: - 小型项目或原型开发:优先选择 Zustand - 大型企业应用:可以考虑 Redux(尤其是需要中间件生态) - 性能敏感的应用:Zustand 可能更有优势 - 团队熟悉度:考虑团队对不同库的熟悉程度
服务端 · 3月7日 11:44
如何在 Zustand 中使用 TypeScript 确保类型安全?### Zustand 中使用 TypeScript 的方法: 1. **定义状态类型** ```typescript interface UserState { user: { id: string; name: string; email: string; } | null; isLoading: boolean; error: string | null; setUser: (user: UserState['user']) => void; setLoading: (loading: boolean) => void; setError: (error: string | null) => void; logout: () => void; } ``` 2. **创建类型化的 Store** ```typescript import { create } from 'zustand'; const useUserStore = create<UserState>((set) => ({ user: null, isLoading: false, error: null, setUser: (user) => set({ user }), setLoading: (loading) => set({ loading }), setError: (error) => set({ error }), logout: () => set({ user: null, error: null }), })); ``` 3. **在组件中使用** ```typescript import { useUserStore } from './store'; function UserProfile() { const user = useUserStore((state) => state.user); const isLoading = useUserStore((state) => state.isLoading); const error = useUserStore((state) => state.error); const logout = useUserStore((state) => state.logout); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>; if (!user) return <div>Please login</div>; return ( <div> <h1>Welcome, {user.name}!</h1> <p>Email: {user.email}</p> <button onClick={logout}>Logout</button> </div> ); } ``` 4. **类型推断和自动补全** - TypeScript 会自动推断状态和操作的类型 - 编辑器会提供智能提示和类型检查 5. **复杂状态类型** - 对于嵌套状态,可以使用嵌套接口 - 对于动态状态,可以使用泛型 6. **中间件的类型支持** - persist 中间件的类型配置 - 自定义中间件的类型定义 ### 最佳实践: - 为每个 store 创建单独的类型定义文件 - 使用接口明确状态结构和操作签名 - 利用 TypeScript 的类型检查捕获潜在错误 - 结合泛型处理复杂的状态类型
服务端 · 3月7日 11:44
如何在 Zustand 中处理异步操作?### Zustand 中处理异步操作的方法: 1. **基本异步操作** - 在 store 中定义异步 action - 使用 async/await 语法 - 示例: ```javascript import { create } from 'zustand'; const useUserStore = create((set) => ({ user: null, isLoading: false, error: null, fetchUser: async (userId) => { set({ isLoading: true, error: null }); try { const response = await fetch(`https://api.example.com/users/${userId}`); const user = await response.json(); set({ user, isLoading: false }); } catch (error) { set({ error: error.message, isLoading: false }); } }, })); ``` 2. **使用 Promise** - 返回 Promise 以便组件可以等待操作完成 - 示例: ```javascript fetchUser: async (userId) => { set({ isLoading: true, error: null }); try { const response = await fetch(`https://api.example.com/users/${userId}`); const user = await response.json(); set({ user, isLoading: false }); return user; // 返回结果 } catch (error) { set({ error: error.message, isLoading: false }); throw error; // 抛出错误 } }, ``` 3. **处理多个异步操作** - 并行执行多个异步操作 - 示例: ```javascript fetchMultipleData: async () => { set({ isLoading: true }); try { const [user, posts] = await Promise.all([ fetch('https://api.example.com/user').then(res => res.json()), fetch('https://api.example.com/posts').then(res => res.json()) ]); set({ user, posts, isLoading: false }); } catch (error) { set({ error: error.message, isLoading: false }); } }, ``` 4. **中间件处理** - 使用自定义中间件处理异步操作 - 示例: ```javascript const asyncMiddleware = (store) => (next) => (action) => { if (typeof action === 'function') { return action(store.getState, store.setState); } return next(action); }; const useStore = create( asyncMiddleware((set, get) => ({ // 状态和操作 })) ); ``` 5. **最佳实践** - 始终处理加载状态和错误状态 - 为异步操作提供取消机制 - 合理使用 try/catch 捕获错误 - 考虑使用 SWR 或 React Query 处理复杂的异步数据 6. **常见异步场景** - API 调用 - 数据加载和缓存 - 文件上传下载 - 认证和授权操作
服务端 · 3月7日 11:44
Zustand 的中间件有哪些,如何使用它们?### Zustand 常用中间件: 1. **persist 中间件** - 功能:将状态持久化到 localStorage、sessionStorage 或自定义存储 - 使用场景:需要保持用户登录状态、用户偏好设置等 - 示例: ```javascript import { create } from 'zustand'; import { persist } from 'zustand/middleware'; const useStore = create( persist( (set) => ({ user: null, setUser: (user) => set({ user }), }), { name: 'user-storage', // 存储名称 } ) ); ``` 2. **devtools 中间件** - 功能:集成 Redux DevTools,方便调试状态变化 - 使用场景:开发环境中调试状态管理 - 示例: ```javascript import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; const useStore = create( devtools( (set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), }) ) ); ``` 3. **immer 中间件** - 功能:使用 Immer 库简化不可变状态更新 - 使用场景:处理复杂的嵌套状态更新 - 示例: ```javascript import { create } from 'zustand'; import { immer } from 'zustand/middleware/immer'; const useStore = create( immer((set) => ({ user: { name: 'John', age: 30 }, updateName: (name) => set((state) => { state.user.name = name; }), })) ); ``` 4. **combine 中间件** - 功能:组合多个状态切片 - 使用场景:模块化管理复杂状态 ### 中间件组合使用: ```javascript import { create } from 'zustand'; import { persist, devtools } from 'zustand/middleware'; const useStore = create( devtools( persist( (set) => ({ // 状态和操作 }), { name: 'app-storage' } ) ) ); ``` ### 自定义中间件: 可以根据需要创建自定义中间件,例如日志记录、性能监控等。
服务端 · 3月7日 11:43