前端开发
在 React/Next.js 客户端需要数据获取、缓存…
适用于使用 React Hooks 管理客户端数据获取、缓存、自动/手动重新验证、错误加载状态、分页与预取的场景。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在 React/Next.js 客户端需特定
解决思路
- 对所有客户端数据获取使用 SWR,而不是用 useEffect 手写请求和状态管理。
- 利用 SWR 自动缓存和重新验证:先返回缓存数据(stale),再发送请求(revalidate),最后交付最新数据。
- 使用 SWR 内置状态管理减少样板代码。
- 实现完善的错误处理和加载状态。
- 使用 TypeScript 为获取的数据提供完整类型安全。
- 利用 Stale-While-Revalidate:立即返回缓存数据,然后在后台重新验证。
- 利用自动重新验证:在组件挂载、窗口聚焦、网络重连时触发。
- 利用请求去重:多个组件使用相同 key 时共享一次请求。
- 利用内置缓存:零配置缓存与智能失效。
- 使用最小 hook API:通过 useSWR 等 hook 接口获取数据。
- 按以下结构组织代码:src/hooks/swr/useUser.ts、usePosts.ts、useProducts.ts;src/lib/fetcher.ts;src/providers/SWRProvider.tsx;src/types/api.ts。
- 在 providers/SWRProvider.tsx 中用 SWRConfig 设置全局配置:fetcher、revalidateOnFocus: true、revalidateOnReconnect: true、shouldRetryOnError: true、errorRetryCount: 3、dedupingInterval: 2000。
- 在 lib/fetcher.ts 中实现全局 fetcher:定义 fetcher<T>(url),调用 fetch(url),若 !res.ok 则抛出错误,否则返回 res.json()。
- 实现带认证的 authFetcher<T>(url):通过 getAuthToken() 获取 token,设置 headers 的 'Content-Type': 'application/json' 和 'Authorization': `Bearer ${token}`,若 !response.ok 则抛出错误,否则返回 response.json()。
- 使用 useSWR Hook 时传入三个参数:key(唯一字符串标识,如 URL)、fetcher(异步获取函数)、options(配置项)。
- 在自定义 hook(如 useUser)中调用 useSWR<User>(userId ? `/api/users/${userId}` : null, fetcher),并返回 user、isLoading、isError、isValidating、mutate。
- 条件获取:当依赖数据不可用时,将 key 传为 null 或 falsy 值以跳过请求。
- 条件获取:使用函数动态生成 key,例如 useSWR(() => `/api/users/${userId}`, fetcher)。
- 自动重新验证默认在组件挂载(即使有缓存数据)、窗口获得焦点、浏览器恢复网络连接时执行。
- 按需禁用特定重新验证:在 useSWR options 中设置 revalidateOnFocus: false、revalidateOnReconnect: false、revalidateOnMount: true 等。
- 手动重新验证:使用 useSWRConfig 的 mutate,await mutate('/api/users') 重新验证指定 key。
- 手动批量重新验证:await mutate((key) => typeof key === 'string' && key.startsWith('/api/users'), undefined, { revalidate: true })。
- 轮询/定时刷新:在 useSWR options 中设置 refreshInterval: 3000 每 3 秒刷新。
- 条件轮询:设置 refreshInterval: isVisible ? 1000 : 0。
- 乐观更新:使用 useSWRConfig 的 mutate,对指定 key 传入 async currentData => { 请求 API 更新,如 fetch(`/api/users/${userId}`, { method: 'PATCH', body: JSON.stringify(newData) }).then(r => r.json());返回 updated },并配置 optimisticData: (current) => ({ ...current, ...newData } as User)、rollbackOnError: true、populateCache: true、revalidate: false。
- 使用 bound mutate:从 useSWR 返回的 mutate 先立即更新本地数据 mutate({ ...user, name: newName }, false),再发送服务器更新,最后调用 mutate() 重新验证保证一致性。
- 基本分页:useSWR(`/api/users?page=${page}`, fetcher, { keepPreviousData: true }) 保留上一页数据。
- 无限加载:使用 useSWRInfinite(getKey, fetcher) 和 getKey(pageIndex, previousPageData);若 previousPageData 存在且为空则 return null 停止获取,否则返回 `/api/users?page=${pageIndex + 1}`。
- 处理无限加载返回值:从 useSWRInfinite 解构 data、error、size、setSize、isValidating;用 data ? data.flat() : [] 得到 users;计算 isLoadingMore、isEmpty、isReachingEnd;用 loadMore: () => setSize(size + 1) 加载更多。
- 基于游标的分页:在 getKey 中,如果 previousPageData && !previousPageData.nextCursor 则 return null;pageIndex === 0 时返回 '/api/items';否则返回 `/api/items?cursor=${previousPageData.nextCursor}`。
- 组件级错误处理:isLoading 时返回 Skeleton;error 时返回 ErrorMessage 并提供 retry={() => mutate(`/api/users/${userId}`)};否则返回 ProfileCard user={data}。
- 全局错误处理:在 SWRConfig 的 onError: (error, key) => { if (error.status !== 403 && error.status !== 404) { reportError(error, key); } } 中上报错误。
- 预取:使用 preload(`/api/users/${userId}`, fetcher) 在组件渲染前预取数据。
- 在 hover 或路由变化时预取:例如 Link 的 onMouseEnter={() => prefetchUser(userId)}。
- 遵循数据获取约定:使用 SWR 进行客户端数据获取并保持最小配置。
- 遵循渲染约定:对加载状态和错误消息实现条件渲染。
- 遵循数据校验约定:校验数据以确保符合预期格式,并优雅处理错误。
- 遵循性能约定:在用户请求数据前使用预取提升性能。
- 遵循实时性约定:利用自动重新验证保持数据新鲜度。
- 遵循分页约定:分页和无限滚动模式使用 useSWRInfinite。
- 集成 Next.js:服务端数据使用 getServerSideProps 或 getStaticProps;客户端缓存和重新验证使用 SWR。
- 在 Next.js 页面 pages/user/[id].tsx 中,于 getStaticProps 调用 fetchUser(params.id),返回 { props: { fallback: { [`/api/users/${params.id}`]: user } } };在 UserPage({ fallback }) 中用 <SWRConfig value={{ fallback }}><UserProfile /></SWRConfig> 注入 fallback。
适用边界 · 注意事项
- 当需要 WebSocket 或 SSE 等推送式实时更新时,不要用 SWR 轮询替代。
- 当服务端渲染或静态生成已直接提供数据,且不需要客户端缓存、自动重新验证或请求去重时,不必强行引入 SWR。
- 当依赖数据未就绪时不要直接发请求,应使用条件获取;同时不要忽略错误状态、加载状态和获取数据的 TypeScript 类型,也不要在 SWR 能处理时用 useEffect 手写数据获取。