swr

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SWR 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 types
src/
  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
useSWR
hook accepts three parameters:
  • 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
Hook接受三个参数:
  • 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
null
or a falsy value as key to conditionally skip fetching:
typescript
// 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);
null
或假值作为key,可条件性跳过数据获取:
typescript
// 仅当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:
  1. Component is mounted (even with cached data)
  2. Window gains focus
  3. Browser regains network connection
typescript
// Disable specific revalidation behaviors
const { data } = useSWR('/api/data', fetcher, {
  revalidateOnFocus: false,
  revalidateOnReconnect: false,
  revalidateOnMount: true,
});
SWR会在三种场景下自动重新验证数据:
  1. 组件挂载(即使已有缓存数据)
  2. 窗口获得焦点
  3. 浏览器重新连接网络
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

关键约定

  1. Use SWR for client-side data fetching with minimal configuration
  2. Implement conditional rendering for loading states and error messages
  3. Validate data to ensure expected format and handle errors gracefully
  4. Use prefetching to improve performance before users request data
  5. Leverage automatic revalidation for real-time data freshness
  6. Use
    useSWRInfinite
    for paginated and infinite scroll patterns
  1. 使用SWR进行客户端数据获取,尽可能减少配置
  2. 为加载状态与错误信息实现条件渲染
  3. 验证数据格式,优雅处理错误
  4. 使用预获取提升用户请求前的性能
  5. 利用自动重新验证保证数据实时性
  6. 使用
    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
    useEffect
    for data fetching when SWR can handle it
  • 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类型定义