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

React面试题手册

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

如何在 Zustand 中使用 TypeScript 进行类型定义?

在 Zustand 中使用 TypeScript 的方法:基本类型定义:import { create } from 'zustand';// 定义状态和动作的类型interface StoreState { // 状态 count: number; user: { id: string; name: string; email: string; } | null; // 动作 increment: () => void; decrement: () => void; setUser: (user: StoreState['user']) => void; reset: () => void;}// 创建带类型的 storeconst useStore = create<StoreState>((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 }),}));使用 get 的类型定义:import { create } from 'zustand';interface StoreState { count: number; user: { id: string; name: string; } | null; increment: () => void; incrementBy: (value: number) => void; fetchUser: (userId: string) => Promise<void>;}const useStore = create<StoreState>((set, get) => ({ count: 0, user: null, increment: () => set((state) => ({ count: state.count + 1 })), incrementBy: (value) => set((state) => ({ count: state.count + value })), // 使用 get 获取最新状态 fetchUser: async (userId) => { try { const response = await fetch(`/api/users/${userId}`); const userData = await response.json(); set({ user: userData }); // 使用 get 获取最新状态 console.log('Current count:', get().count); } catch (error) { console.error('Error fetching user:', error); } },}));使用中间件时的类型定义:import { create } from 'zustand';import { persist, PersistOptions } from 'zustand/middleware';interface StoreState { count: number; user: { id: string; name: string; } | null; increment: () => void; setUser: (user: StoreState['user']) => void;}// 定义 persist 中间件的类型type StorePersist = { persist: { clearStorage: () => void; };};// 完整的 store 类型type Store = StoreState & StorePersist;// persist 配置类型type PersistConfig = PersistOptions<StoreState, StorePersist>;const useStore = create<Store>( persist<StoreState, StorePersist>( (set) => ({ count: 0, user: null, increment: () => set((state) => ({ count: state.count + 1 })), setUser: (user) => set({ user }), }), { name: 'my-storage', } as PersistConfig ));使用 selectors 的类型定义:import { create } from 'zustand';import { StoreState } from './store';// 在组件中使用function Counter() { // 类型安全的 selector const count = useStore((state: StoreState) => state.count); const increment = useStore((state: StoreState) => state.increment); return ( <div> <h1>Count: {count}</h1> <button onClick={increment}>Increment</button> </div> );}关键点:使用 TypeScript 接口定义状态和动作的类型在 create 函数中指定泛型类型为 get 和 set 函数提供正确的类型在使用中间件时,需要正确定义中间件的类型确保 selector 函数的返回类型与使用场景匹配TypeScript 可以帮助捕获状态更新和使用中的类型错误
阅读 0·3月6日 22:00

如何在 Zustand 中使用中间件?

安装必要的依赖(如果使用 persist 中间件):npm install zustand persist# 或yarn add zustand persist在 store 中使用中间件:import { create } from 'zustand';import { persist } from 'zustand/middleware';const useStore = create( persist( (set, get) => ({ // 状态 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 }), // 使用 get 获取当前状态 incrementBy: (value) => set((state) => ({ count: state.count + value })) }), { // persist 配置 name: 'my-storage', // 存储名称 storage: localStorage, // 存储方式(默认 localStorage) // 可选:部分持久化 partialize: (state) => ({ user: state.user }), // 可选:转换函数 serialize: (state) => JSON.stringify(state), deserialize: (str) => JSON.parse(str) } ));export default useStore;使用 devtools 中间件:import { create } from 'zustand';import { devtools } from 'zustand/middleware';const useStore = create( devtools( (set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })) }), { name: 'my-store', // 在 Redux DevTools 中的名称 enabled: true // 是否启用 } ));组合多个中间件:import { create } from 'zustand';import { persist, devtools } from 'zustand/middleware';const useStore = create( devtools( persist( (set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })) }), { name: 'my-storage' } ), { name: 'my-store' } ));关键点:Zustand 中间件通过函数组合的方式使用persist 中间件可以将状态持久化到 localStorage、sessionStorage 等devtools 中间件可以在 Redux DevTools 中查看状态变化中间件的顺序很重要,通常 devtools 在外层,persist 在内层可以自定义中间件来实现特定的功能
阅读 0·3月6日 21:59

Zustand 与 Redux 相比有哪些优缺点?

Zustand 的优点:更简单的 API无需定义 action types、reducers、action creators代码量减少 60-70%学习曲线更低无需 Provider不需要在应用顶层包裹 Provider 组件减少了组件树的嵌套层级更容易集成到现有项目中更好的性能内置选择性订阅机制自动优化重渲染无需手动使用 useSelector 或 connect更小的体积仅约 1KB gzippedRedux Toolkit 约 12KB gzippedTypeScript 友好内置完整的 TypeScript 支持类型推断更准确Zustand 的缺点:生态系统较小中间件和扩展库相对较少社区资源不如 Redux 丰富调试工具有限虽然 Redux DevTools 可以使用,但功能不如 Redux 完善时间旅行调试支持有限大型项目经验较少在超大型企业级应用中的实践案例较少最佳实践仍在发展中团队熟悉度开发者对 Redux 更熟悉招聘和培训成本可能更高选择 Zustand 的场景:中小型项目项目规模不大,状态管理需求简单不需要复杂的状态管理架构快速原型开发需要快速搭建和迭代重视开发速度而非架构完整性性能敏感的应用需要最小化重渲染对包大小有严格要求团队偏好简洁团队成员熟悉 hooks 模式希望减少样板代码选择 Redux 的场景:大型企业级应用需要严格的状态管理架构团队规模大,需要标准化流程复杂的状态逻辑需要时间旅行调试状态更新逻辑复杂,需要规范化团队已有 Redux 经验团队成员熟悉 Redux已有相关的工具和基础设施总结:Zustand 更适合现代 React 开发,特别是中小型项目和追求简洁的场景。Redux 更适合大型企业级应用和需要严格状态管理的场景。
阅读 0·3月6日 21:43

如何优化 React Query 的性能,有哪些常见的性能瓶颈和解决方案?

React Query 本身已经做了很多性能优化,但在大型应用中,仍然需要注意以下几点来进一步提升性能:常见性能瓶颈过度重新渲染:当查询数据更新时,可能导致组件不必要的重新渲染过多的并发查询:同时执行大量查询可能影响应用性能缓存配置不当:缓存策略不合理可能导致重复请求或内存占用过高查询键设计不当:过于复杂或频繁变化的查询键可能影响缓存效率大数据集处理:处理大量数据时的性能问题性能优化策略合理使用缓存配置staleTime:对于不常变化的数据,设置较长的 staleTimecacheTime:对于不常用的数据,设置较短的 cacheTime示例: javascript const { data } = useQuery('users', fetchUsers, { staleTime: 10 * 60 * 1000, // 10分钟 cacheTime: 30 * 60 * 1000, // 30分钟 });优化查询键使用稳定的查询键避免在查询键中包含频繁变化的值合理组织查询键的层次结构使用查询结果选择器只订阅组件需要的数据,减少不必要的重新渲染示例: javascript const { data: userName } = useQuery('user', fetchUser, { select: (data) => data.name, });批量查询和预取使用 useQueries 批量处理多个查询合理使用预取功能,提前获取可能需要的数据分页和无限滚动对于大数据集,使用分页或无限滚动避免一次性获取所有数据示例: javascript const { data, fetchNextPage, hasNextPage } = useInfiniteQuery( ['posts'], ({ pageParam = 1 }) => fetchPosts(pageParam), { getNextPageParam: (lastPage, pages) => lastPage.nextCursor, } );禁用不必要的特性根据需要禁用 refetchOnWindowFocus、refetchOnMount 等示例: javascript const { data } = useQuery('todos', fetchTodos, { refetchOnWindowFocus: false, refetchOnMount: false, });使用 React.memo 和 useMemo包装组件以避免不必要的重新渲染缓存计算结果监控和调试使用 React Query DevTools 监控查询状态和性能分析查询的执行时间和频率高级优化技巧查询合并:对于相似的查询,考虑合并为单个查询自定义缓存策略:根据业务需求实现自定义缓存逻辑使用持久化缓存:对于需要跨会话保存的数据,使用持久化缓存后台数据同步:使用后台同步功能,在空闲时更新数据通过合理应用这些优化策略,可以显著提升 React Query 在大型应用中的性能表现。
阅读 0·3月6日 21:34

React Query 的缓存机制是如何工作的,如何配置和管理缓存?

React Query 的缓存机制是其核心特性之一,它通过以下方式工作:缓存工作原理查询键(Query Keys):React Query 使用查询键(通常是字符串或数组)作为缓存的唯一标识符。相同查询键的请求会共享缓存数据。缓存存储:查询结果存储在内存中,按查询键组织。缓存状态:每个缓存项包含数据、时间戳、状态(新鲜/过期/失效)等信息。缓存生命周期:新鲜期(Fresh):数据最近获取,无需重新请求过期期(Stale):数据超过 staleTime,但未超过 gcTime失效期(Invalidated):数据被手动标记为失效垃圾回收:超过 gcTime 的数据会被从缓存中移除缓存配置全局配置:通过 QueryClient 配置默认缓存行为 const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, // 5分钟 cacheTime: 10 * 60 * 1000, // 10分钟 retry: 3, }, }, });单个查询配置:在 useQuery 中覆盖默认配置 const { data } = useQuery('todos', fetchTodos, { staleTime: 10 * 60 * 1000, cacheTime: 30 * 60 * 1000, refetchOnWindowFocus: false, });缓存管理数据失效:手动标记查询为失效,触发重新获取 queryClient.invalidateQueries('todos');数据更新:直接更新缓存中的数据 queryClient.setQueryData('todos', oldTodos => [...oldTodos, newTodo]);数据预取:提前获取可能需要的数据 queryClient.prefetchQuery('user-1', () => fetchUser(1));缓存清除:从缓存中移除数据 queryClient.removeQueries('todos');缓存重置:清除所有缓存数据 queryClient.resetQueries();缓存策略最佳实践根据数据的更新频率设置合适的 staleTime对于频繁访问但不常变化的数据,增加 cacheTime利用预取提升用户体验在 mutations 后正确处理相关查询的失效使用查询键的层次结构(如 ['users', userId, 'todos'])实现更精细的缓存控制通过合理配置和管理缓存,可以显著提高应用性能,减少不必要的网络请求,同时确保数据的新鲜度。
阅读 0·3月6日 21:34

React Native 如何进行调试?常用的调试工具有哪些?

React Native 调试方式概览React Native 提供了多种调试方式,从简单的控制台输出到专业的性能分析工具,开发者可以根据问题类型选择合适的调试手段。1. 控制台调试(Console Debugging)最基础的调试方式,适用于快速验证。// 基础日志console.log('普通日志');console.warn('警告信息');console.error('错误信息');// 格式化输出console.table([{ name: '张三', age: 25 }, { name: '李四', age: 30 }]);console.group('分组日志');console.log('子日志1');console.log('子日志2');console.groupEnd();查看方式:Metro 终端直接输出Chrome DevTools Console 面板Flipper 日志查看器2. Chrome DevTools 调试最常用的 JS 调试方式,支持断点、单步执行等。开启方式iOS: Cmd + D → Debug with ChromeAndroid: Cmd + M / 摇一摇 → Debug with Chrome调试功能Sources:设置断点、单步调试Console:查看日志、执行命令Network:监控网络请求Performance:分析性能Memory:内存分析注意事项开启 Remote Debugging 后,JS 在 Chrome 中运行,不在设备上运行某些原生功能在调试模式下可能表现不同网络请求会被代理到 Chrome3. React Native Debugger专为 React Native 打造的独立调试工具,集成了 Redux DevTools。安装与使用# macOSbrew install react-native-debugger# 启动open "rndebugger://set-debugger-loc?host=localhost&port=8081"核心功能集成 Chrome DevToolsRedux DevTools 支持Apollo Client DevTools网络请求拦截AsyncStorage 查看器4. Flipper(Meta 官方推荐)现代化的移动应用调试平台,React Native 0.62+ 默认集成。核心插件| 插件 | 功能 || ------------------ | -------------------- || Logs | 查看应用日志 || Network | 监控 HTTP 请求 || React DevTools | 组件树查看、Props/State 检查 || Hermes Debugger | Hermes 引擎调试 || Databases | 查看 SQLite/Realm 数据 || Shared Preferences | 查看应用偏好设置 || Crash Reporter | 崩溃日志收集 |使用示例// 添加 Flipper 网络插件支持import { addEventListener } from '@react-native-community/netinfo';// 在 Flipper 中查看网络请求fetch('https://api.example.com/data') .then(response => response.json()) .then(data => console.log(data));5. React DevTools用于检查 React 组件树和性能分析。独立安装npm install -g react-devtoolsreact-devtools功能特性查看组件树结构检查 Props 和 State识别不必要的重渲染Profiler 性能分析6. 原生调试iOS 调试(Xcode)1. 用 Xcode 打开 ios/YourApp.xcworkspace2. 选择设备和 Scheme3. Cmd + R 运行4. 使用 Xcode 调试器设置断点Android 调试(Android Studio)1. 用 Android Studio 打开 android 目录2. 连接设备或启动模拟器3. 点击 Debug 按钮运行4. 使用 Logcat 查看日志7. 性能调试Hermes 性能分析// 启用 Hermes 采样分析器const HermesInternal = require('hermes-engine');// 开始采样HermesInternal.enableSamplingProfiler();// 停止并导出HermesInternal.dumpSampledTraceToFile('/path/to/trace.json');使用 Flipper Performance 插件监控 FPS(帧率)检测 UI 卡顿分析渲染时间8. 常用调试技巧热重载与实时重载Cmd + D / 摇一摇 → Enable Hot Reloading清除缓存# 清除 Metro 缓存npx react-native start --reset-cache# 清除 iOS 构建缓存cd ios && rm -rf build && cd ..# 清除 Android 构建缓存cd android && ./gradlew clean && cd ..网络调试// 查看所有网络请求GLOBAL.XMLHttpRequest = GLOBAL.originalXMLHttpRequest || GLOBAL.XMLHttpRequest;调试工具选择建议| 场景 | 推荐工具 || ---------- | ------------------------------------- || 日常 JS 调试 | Chrome DevTools / Flipper || Redux 状态调试 | React Native Debugger || UI 问题排查 | Flipper + React DevTools || 性能优化 | Flipper Performance + Hermes Profiler || 原生问题 | Xcode / Android Studio || 网络调试 | Flipper Network 插件 |调试最佳实践使用 TypeScript:编译时捕获类型错误添加错误边界:防止应用崩溃使用 Sentry/Firebase:线上错误监控日志分级:开发/生产环境不同日志级别定期性能测试:使用 Flipper 检测性能回归
阅读 0·3月6日 21:27