swr
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSWR Best Practices
SWR最佳实践
You are an expert in SWR (stale-while-revalidate), TypeScript, and React development. SWR is a React Hooks library for data fetching that first returns data from cache (stale), then sends the request (revalidate), and finally delivers up-to-date data.
您是SWR(stale-while-revalidate)、TypeScript和React开发领域的专家。SWR是一款用于数据获取的React Hooks库,它会先从缓存返回旧数据(stale),然后发送请求进行重新验证(revalidate),最后提供最新数据。
Core Principles
核心原则
- Use SWR for all client-side data fetching
- Leverage automatic caching and revalidation
- Minimize boilerplate with SWR's built-in state management
- Implement proper error handling and loading states
- Use TypeScript for full type safety
- 所有客户端数据获取均使用SWR
- 利用自动缓存与重新验证机制
- 通过SWR内置的状态管理减少样板代码
- 实现完善的错误处理与加载状态
- 使用TypeScript确保完整的类型安全
Key Features
核心特性
- Stale-While-Revalidate: Returns cached data immediately, then revalidates in background
- Automatic Revalidation: On mount, window focus, and network reconnection
- Request Deduplication: Multiple components using same key share one request
- Built-in Caching: Zero-configuration caching with smart invalidation
- Minimal API: Simple hook-based interface
- Stale-While-Revalidate:立即返回缓存数据,后台执行重新验证
- 自动重新验证:在组件挂载、窗口获得焦点、网络重连时自动触发
- 请求去重:多个使用相同key的组件共享同一个请求
- 内置缓存:零配置缓存,支持智能失效
- 极简API:基于Hook的简洁接口
Project Structure
项目结构
src/
hooks/
swr/
useUser.ts
usePosts.ts
useProducts.ts
lib/
fetcher.ts # Global fetcher configuration
providers/
SWRProvider.tsx # SWR configuration provider
types/
api.ts # API response typessrc/
hooks/
swr/
useUser.ts
usePosts.ts
useProducts.ts
lib/
fetcher.ts # 全局Fetcher配置
providers/
SWRProvider.tsx # SWR配置提供者
types/
api.ts # API响应类型定义Setup and Configuration
安装与配置
Global Configuration
全局配置
typescript
// providers/SWRProvider.tsx
import { SWRConfig } from 'swr';
const fetcher = async (url: string) => {
const res = await fetch(url);
if (!res.ok) {
const error = new Error('An error occurred while fetching data.');
throw error;
}
return res.json();
};
export function SWRProvider({ children }: { children: React.ReactNode }) {
return (
<SWRConfig
value={{
fetcher,
revalidateOnFocus: true,
revalidateOnReconnect: true,
shouldRetryOnError: true,
errorRetryCount: 3,
dedupingInterval: 2000,
}}
>
{children}
</SWRConfig>
);
}typescript
// providers/SWRProvider.tsx
import { SWRConfig } from 'swr';
const fetcher = async (url: string) => {
const res = await fetch(url);
if (!res.ok) {
const error = new Error('数据获取时发生错误。');
throw error;
}
return res.json();
};
export function SWRProvider({ children }: { children: React.ReactNode }) {
return (
<SWRConfig
value={{
fetcher,
revalidateOnFocus: true,
revalidateOnReconnect: true,
shouldRetryOnError: true,
errorRetryCount: 3,
dedupingInterval: 2000,
}}
>
{children}
</SWRConfig>
);
}Custom Fetcher
自定义Fetcher
typescript
// lib/fetcher.ts
export async function fetcher<T>(url: string): Promise<T> {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
// With authentication
export async function authFetcher<T>(url: string): Promise<T> {
const token = getAuthToken();
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}typescript
// lib/fetcher.ts
export async function fetcher<T>(url: string): Promise<T> {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP错误!状态码: ${response.status}`);
}
return response.json();
}
// 带认证的Fetcher
export async function authFetcher<T>(url: string): Promise<T> {
const token = getAuthToken();
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error(`HTTP错误!状态码: ${response.status}`);
}
return response.json();
}Basic Usage
基础用法
useSWR Hook
useSWR Hook
The hook accepts three parameters:
useSWR- key: A unique string identifier for the request (like a URL)
- fetcher: An async function that fetches the data
- options: Configuration options
typescript
import useSWR from 'swr';
interface User {
id: string;
name: string;
email: string;
}
function useUser(userId: string) {
const { data, error, isLoading, isValidating, mutate } = useSWR<User>(
userId ? `/api/users/${userId}` : null,
fetcher
);
return {
user: data,
isLoading,
isError: !!error,
isValidating,
mutate,
};
}useSWR- key:请求的唯一字符串标识(如URL)
- fetcher:用于获取数据的异步函数
- options:配置选项
typescript
import useSWR from 'swr';
interface User {
id: string;
name: string;
email: string;
}
function useUser(userId: string) {
const { data, error, isLoading, isValidating, mutate } = useSWR<User>(
userId ? `/api/users/${userId}` : null,
fetcher
);
return {
user: data,
isLoading,
isError: !!error,
isValidating,
mutate,
};
}Conditional Fetching
条件式获取
Pass or a falsy value as key to conditionally skip fetching:
nulltypescript
// Only fetch when userId is available
const { data } = useSWR(userId ? `/api/users/${userId}` : null, fetcher);
// Using a function for dynamic keys
const { data } = useSWR(() => `/api/users/${userId}`, fetcher);将或假值作为key,可条件性跳过数据获取:
nulltypescript
// 仅当userId存在时才获取数据
const { data } = useSWR(userId ? `/api/users/${userId}` : null, fetcher);
// 使用函数生成动态key
const { data } = useSWR(() => `/api/users/${userId}`, fetcher);Revalidation Strategies
重新验证策略
Automatic Revalidation
自动重新验证
SWR automatically revalidates data in three cases:
- Component is mounted (even with cached data)
- Window gains focus
- Browser regains network connection
typescript
// Disable specific revalidation behaviors
const { data } = useSWR('/api/data', fetcher, {
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateOnMount: true,
});SWR会在三种场景下自动重新验证数据:
- 组件挂载(即使已有缓存数据)
- 窗口获得焦点
- 浏览器重新连接网络
typescript
// 禁用特定的重新验证行为
const { data } = useSWR('/api/data', fetcher, {
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateOnMount: true,
});Manual Revalidation
手动重新验证
typescript
import { useSWRConfig } from 'swr';
function UpdateButton() {
const { mutate } = useSWRConfig();
const handleUpdate = async () => {
// Revalidate specific key
await mutate('/api/users');
// Revalidate all keys matching a filter
await mutate(
(key) => typeof key === 'string' && key.startsWith('/api/users'),
undefined,
{ revalidate: true }
);
};
return <button onClick={handleUpdate}>Refresh</button>;
}typescript
import { useSWRConfig } from 'swr';
function UpdateButton() {
const { mutate } = useSWRConfig();
const handleUpdate = async () => {
// 重新验证指定key
await mutate('/api/users');
// 重新验证所有匹配过滤条件的key
await mutate(
(key) => typeof key === 'string' && key.startsWith('/api/users'),
undefined,
{ revalidate: true }
);
};
return <button onClick={handleUpdate}>刷新</button>;
}Polling / Interval Refresh
轮询/定时刷新
typescript
// Refresh every 3 seconds
const { data } = useSWR('/api/realtime', fetcher, {
refreshInterval: 3000,
});
// Conditional polling
const { data } = useSWR('/api/realtime', fetcher, {
refreshInterval: isVisible ? 1000 : 0,
});typescript
// 每3秒刷新一次
const { data } = useSWR('/api/realtime', fetcher, {
refreshInterval: 3000,
});
// 条件式轮询
const { data } = useSWR('/api/realtime', fetcher, {
refreshInterval: isVisible ? 1000 : 0,
});Mutation Patterns
数据更新模式
Optimistic Updates
乐观更新
typescript
import useSWR, { useSWRConfig } from 'swr';
function useUpdateUser() {
const { mutate } = useSWRConfig();
const updateUser = async (userId: string, newData: Partial<User>) => {
// Optimistic update
await mutate(
`/api/users/${userId}`,
async (currentData: User | undefined) => {
// Update API
const updated = await fetch(`/api/users/${userId}`, {
method: 'PATCH',
body: JSON.stringify(newData),
}).then(r => r.json());
return updated;
},
{
optimisticData: (current) => ({ ...current, ...newData } as User),
rollbackOnError: true,
populateCache: true,
revalidate: false,
}
);
};
return { updateUser };
}typescript
import useSWR, { useSWRConfig } from 'swr';
function useUpdateUser() {
const { mutate } = useSWRConfig();
const updateUser = async (userId: string, newData: Partial<User>) => {
// 乐观更新
await mutate(
`/api/users/${userId}`,
async (currentData: User | undefined) => {
// 调用API更新数据
const updated = await fetch(`/api/users/${userId}`, {
method: 'PATCH',
body: JSON.stringify(newData),
}).then(r => r.json());
return updated;
},
{
optimisticData: (current) => ({ ...current, ...newData } as User),
rollbackOnError: true,
populateCache: true,
revalidate: false,
}
);
};
return { updateUser };
}Bound Mutate
绑定式Mutate
typescript
function UserProfile({ userId }: { userId: string }) {
const { data: user, mutate } = useSWR(`/api/users/${userId}`, fetcher);
const handleUpdate = async (newName: string) => {
// Update local data immediately
mutate({ ...user, name: newName }, false);
// Send update to server
await updateUserName(userId, newName);
// Revalidate to ensure consistency
mutate();
};
return (
<div>
<p>{user?.name}</p>
<button onClick={() => handleUpdate('New Name')}>Update</button>
</div>
);
}typescript
function UserProfile({ userId }: { userId: string }) {
const { data: user, mutate } = useSWR(`/api/users/${userId}`, fetcher);
const handleUpdate = async (newName: string) => {
// 立即更新本地数据
mutate({ ...user, name: newName }, false);
// 向服务器发送更新请求
await updateUserName(userId, newName);
// 重新验证以确保数据一致性
mutate();
};
return (
<div>
<p>{user?.name}</p>
<button onClick={() => handleUpdate('新名称')}>更新</button>
</div>
);
}Pagination
分页
Basic Pagination
基础分页
typescript
function usePaginatedUsers(page: number) {
return useSWR(`/api/users?page=${page}`, fetcher, {
keepPreviousData: true,
});
}typescript
function usePaginatedUsers(page: number) {
return useSWR(`/api/users?page=${page}`, fetcher, {
keepPreviousData: true,
});
}Infinite Loading with useSWRInfinite
使用useSWRInfinite实现无限加载
typescript
import useSWRInfinite from 'swr/infinite';
function useInfiniteUsers() {
const getKey = (pageIndex: number, previousPageData: User[] | null) => {
// Return null to stop fetching
if (previousPageData && previousPageData.length === 0) return null;
// Return key for next page
return `/api/users?page=${pageIndex + 1}`;
};
const { data, error, size, setSize, isValidating } = useSWRInfinite(
getKey,
fetcher
);
const users = data ? data.flat() : [];
const isLoadingMore = size > 0 && data && typeof data[size - 1] === 'undefined';
const isEmpty = data?.[0]?.length === 0;
const isReachingEnd = isEmpty || (data && data[data.length - 1]?.length < 10);
return {
users,
isLoadingMore,
isReachingEnd,
loadMore: () => setSize(size + 1),
};
}typescript
import useSWRInfinite from 'swr/infinite';
function useInfiniteUsers() {
const getKey = (pageIndex: number, previousPageData: User[] | null) => {
// 返回null停止获取下一页
if (previousPageData && previousPageData.length === 0) return null;
// 返回下一页的key
return `/api/users?page=${pageIndex + 1}`;
};
const { data, error, size, setSize, isValidating } = useSWRInfinite(
getKey,
fetcher
);
const users = data ? data.flat() : [];
const isLoadingMore = size > 0 && data && typeof data[size - 1] === 'undefined';
const isEmpty = data?.[0]?.length === 0;
const isReachingEnd = isEmpty || (data && data[data.length - 1]?.length < 10);
return {
users,
isLoadingMore,
isReachingEnd,
loadMore: () => setSize(size + 1),
};
}Cursor-Based Pagination
基于游标(Cursor)的分页
typescript
import useSWRInfinite from 'swr/infinite';
function useCursorPagination() {
const getKey = (pageIndex: number, previousPageData: any) => {
if (previousPageData && !previousPageData.nextCursor) return null;
if (pageIndex === 0) return '/api/items';
return `/api/items?cursor=${previousPageData.nextCursor}`;
};
return useSWRInfinite(getKey, fetcher);
}typescript
import useSWRInfinite from 'swr/infinite';
function useCursorPagination() {
const getKey = (pageIndex: number, previousPageData: any) => {
if (previousPageData && !previousPageData.nextCursor) return null;
if (pageIndex === 0) return '/api/items';
return `/api/items?cursor=${previousPageData.nextCursor}`;
};
return useSWRInfinite(getKey, fetcher);
}Error Handling
错误处理
Component-Level Error Handling
组件级错误处理
typescript
function UserProfile({ userId }: { userId: string }) {
const { data, error, isLoading } = useSWR(`/api/users/${userId}`, fetcher);
if (isLoading) return <Skeleton />;
if (error) {
return (
<ErrorMessage
message="Failed to load user profile"
retry={() => mutate(`/api/users/${userId}`)}
/>
);
}
return <ProfileCard user={data} />;
}typescript
function UserProfile({ userId }: { userId: string }) {
const { data, error, isLoading } = useSWR(`/api/users/${userId}`, fetcher);
if (isLoading) return <Skeleton />;
if (error) {
return (
<ErrorMessage
message="加载用户资料失败"
retry={() => mutate(`/api/users/${userId}`)}
/>
);
}
return <ProfileCard user={data} />;
}Global Error Handler
全局错误处理器
typescript
<SWRConfig
value={{
onError: (error, key) => {
if (error.status !== 403 && error.status !== 404) {
// Send error to tracking service
reportError(error, key);
}
},
}}
>
<App />
</SWRConfig>typescript
<SWRConfig
value={{
onError: (error, key) => {
if (error.status !== 403 && error.status !== 404) {
// 将错误上报至追踪服务
reportError(error, key);
}
},
}}
>
<App />
</SWRConfig>Prefetching
预获取
typescript
import { preload } from 'swr';
// Prefetch data before component renders
function prefetchUser(userId: string) {
preload(`/api/users/${userId}`, fetcher);
}
// Usage: Call on hover or route change
function UserLink({ userId }: { userId: string }) {
return (
<Link
to={`/users/${userId}`}
onMouseEnter={() => prefetchUser(userId)}
>
View Profile
</Link>
);
}typescript
import { preload } from 'swr';
// 在组件渲染前预获取数据
function prefetchUser(userId: string) {
preload(`/api/users/${userId}`, fetcher);
}
// 使用场景:在鼠标悬停或路由切换时调用
function UserLink({ userId }: { userId: string }) {
return (
<Link
to={`/users/${userId}`}
onMouseEnter={() => prefetchUser(userId)}
>
查看资料
</Link>
);
}Key Conventions
关键约定
- Use SWR for client-side data fetching with minimal configuration
- Implement conditional rendering for loading states and error messages
- Validate data to ensure expected format and handle errors gracefully
- Use prefetching to improve performance before users request data
- Leverage automatic revalidation for real-time data freshness
- Use for paginated and infinite scroll patterns
useSWRInfinite
- 使用SWR进行客户端数据获取,尽可能减少配置
- 为加载状态与错误信息实现条件渲染
- 验证数据格式,优雅处理错误
- 使用预获取提升用户请求前的性能
- 利用自动重新验证保证数据实时性
- 使用实现分页与无限滚动模式
useSWRInfinite
Integration with Next.js
与Next.js集成
typescript
// For server-side data, use getServerSideProps or getStaticProps
// For client-side caching and revalidation, use SWR
// pages/user/[id].tsx
export async function getStaticProps({ params }) {
const user = await fetchUser(params.id);
return { props: { fallback: { [`/api/users/${params.id}`]: user } } };
}
export default function UserPage({ fallback }) {
return (
<SWRConfig value={{ fallback }}>
<UserProfile />
</SWRConfig>
);
}typescript
// 服务端数据使用getServerSideProps或getStaticProps
// 客户端缓存与重新验证使用SWR
// pages/user/[id].tsx
export async function getStaticProps({ params }) {
const user = await fetchUser(params.id);
return { props: { fallback: { [`/api/users/${params.id}`]: user } } };
}
export default function UserPage({ fallback }) {
return (
<SWRConfig value={{ fallback }}>
<UserProfile />
</SWRConfig>
);
}Anti-Patterns to Avoid
需要避免的反模式
- Do not use for data fetching when SWR can handle it
useEffect - Do not ignore error states - always provide user feedback
- Do not forget to handle loading states appropriately
- Do not use polling when WebSocket or SSE would be more efficient
- Do not skip conditional fetching for dependent data
- Do not ignore TypeScript types for fetched data
- 当SWR可处理时,不要使用进行数据获取
useEffect - 不要忽略错误状态,始终为用户提供反馈
- 不要忘记合理处理加载状态
- 当WebSocket或SSE更高效时,不要使用轮询
- 不要跳过依赖数据的条件式获取
- 不要忽略获取数据的TypeScript类型定义