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

服务端面试题手册

如何在 Zustand 中优化状态更新和性能?

Zustand 中的性能优化方法:选择性订阅:// 不推荐:订阅整个 store,会导致组件在任何状态变化时都重渲染const { count, user } = useStore();// 推荐:只订阅需要的状态部分const count = useStore((state) => state.count);const user = useStore((state) => state.user);使用 shallow 比较(对于复杂对象):import { create } from 'zustand';import { shallow } from 'zustand/shallow';// 订阅多个状态并使用 shallow 比较const { count, user } = useStore( (state) => ({ count: state.count, user: state.user }), shallow // 只有当 count 或 user 真正变化时才重渲染);状态拆分:// 按功能拆分多个 store// userStore.jsconst useUserStore = create((set) => ({ user: null, setUser: (user) => set({ user })}));// counterStore.jsconst useCounterStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 }))}));使用 get 访问当前状态(避免闭包陷阱):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 }); }}));批量更新:// Zustand 会自动批量处理多个 set 调用const updateMultiple = () => { set({ count: 1 }); set({ user: { name: 'John' } }); // 只会触发一次重渲染};避免在组件渲染时创建新函数:// 不推荐:每次渲染都创建新函数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 会自动处理批量更新
阅读 0·3月7日 12:01

如何在 Zustand 中创建自定义中间件?

在 Zustand 中创建自定义中间件非常灵活,可以用来实现各种功能,如日志记录、状态验证、性能监控等。基本自定义中间件结构: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:日志中间件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:状态验证中间件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:性能监控中间件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:撤销/重做中间件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,添加新的方法或属性
阅读 0·3月7日 12:01

如何在 Zustand 中处理异步操作?

在 Zustand 中处理异步操作的方法:基本异步操作: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 }); } }}));使用 Promise 链: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; }); }}));结合 React Query 或 SWR:// 可以在 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 等库结合使用,获得更好的缓存和失效策略
阅读 0·3月7日 11:48

Zustand 中级面试题:如何对 Zustand store 进行单元测试?

对 Zustand store 进行单元测试相对简单,因为 store 是纯 JavaScript 对象。基本测试示例:// store.jsimport { 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;// store.test.jsimport { 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(); });});测试异步操作:// store.jsconst 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 }); } }}));// store.test.jsimport { 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); });});测试选择性订阅: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
阅读 0·3月7日 11:44

如何在 React Native 中使用 Zustand 管理状态?

React Native 中使用 Zustand 的方法:安装 Zustand npm install zustand # 或 yarn add zustand创建 Store import { create } from 'zustand'; const useCounterStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })), }));在 React Native 组件中使用 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', }, });持久化状态使用 persist 中间件保存状态到 AsyncStorage示例: 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); }, }, } ) );React Native 特定优化避免在选择器中使用复杂计算合理使用 useCallback 缓存回调函数注意 AsyncStorage 的性能影响常见使用场景用户认证状态管理应用设置和偏好购物车状态导航状态管理
阅读 0·3月7日 11:44

如何在 Zustand 中使用 TypeScript 确保类型安全?

Zustand 中使用 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; }创建类型化的 Store 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 }), }));在组件中使用 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> ); }类型推断和自动补全TypeScript 会自动推断状态和操作的类型编辑器会提供智能提示和类型检查复杂状态类型对于嵌套状态,可以使用嵌套接口对于动态状态,可以使用泛型中间件的类型支持persist 中间件的类型配置自定义中间件的类型定义最佳实践:为每个 store 创建单独的类型定义文件使用接口明确状态结构和操作签名利用 TypeScript 的类型检查捕获潜在错误结合泛型处理复杂的状态类型
阅读 0·3月7日 11:44

如何在 Zustand 中处理异步操作?

Zustand 中处理异步操作的方法:基本异步操作在 store 中定义异步 action使用 async/await 语法示例: 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 }); } }, }));使用 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; // 抛出错误 } },处理多个异步操作并行执行多个异步操作示例: 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 }); } },中间件处理使用自定义中间件处理异步操作示例: const asyncMiddleware = (store) => (next) => (action) => { if (typeof action === 'function') { return action(store.getState, store.setState); } return next(action); }; const useStore = create( asyncMiddleware((set, get) => ({ // 状态和操作 })) );最佳实践始终处理加载状态和错误状态为异步操作提供取消机制合理使用 try/catch 捕获错误考虑使用 SWR 或 React Query 处理复杂的异步数据常见异步场景API 调用数据加载和缓存文件上传下载认证和授权操作
阅读 0·3月7日 11:44

Zustand 的中间件有哪些,如何使用它们?

Zustand 常用中间件:persist 中间件功能:将状态持久化到 localStorage、sessionStorage 或自定义存储使用场景:需要保持用户登录状态、用户偏好设置等示例: import { create } from 'zustand'; import { persist } from 'zustand/middleware'; const useStore = create( persist( (set) => ({ user: null, setUser: (user) => set({ user }), }), { name: 'user-storage', // 存储名称 } ) );devtools 中间件功能:集成 Redux DevTools,方便调试状态变化使用场景:开发环境中调试状态管理示例: import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; const useStore = create( devtools( (set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), }) ) );immer 中间件功能:使用 Immer 库简化不可变状态更新使用场景:处理复杂的嵌套状态更新示例: 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; }), })) );combine 中间件功能:组合多个状态切片使用场景:模块化管理复杂状态中间件组合使用:import { create } from 'zustand';import { persist, devtools } from 'zustand/middleware';const useStore = create( devtools( persist( (set) => ({ // 状态和操作 }), { name: 'app-storage' } ) ));自定义中间件:可以根据需要创建自定义中间件,例如日志记录、性能监控等。
阅读 0·3月7日 11:43