react-api

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React API Patterns Core Knowledge

React API调用模式核心知识

Deep Knowledge: Use
mcp__documentation__fetch_docs
with technology:
react
for comprehensive documentation.
深度参考:使用
mcp__documentation__fetch_docs
工具并指定技术为
react
,可获取完整文档。

When NOT to Use This Skill

不适用场景

Skip this skill when:
  • Using TanStack Query exclusively (use
    state-tanstack-query
    )
  • Working with GraphQL (use Apollo Client or urql)
  • Building non-React applications
  • Server Components handle data fetching (Next.js App Router)
  • Need advanced caching beyond SWR (use
    state-tanstack-query
    )
在以下情况请跳过本技能:
  • 仅使用TanStack Query(请使用
    state-tanstack-query
  • 处理GraphQL相关内容(请使用Apollo Client或urql)
  • 构建非React应用
  • 由Server Components处理数据获取(Next.js App Router)
  • 需要超出SWR能力的高级缓存功能(请使用
    state-tanstack-query

Custom useFetch Hook

自定义useFetch Hook

typescript
import { useState, useEffect, useCallback } from 'react';

interface UseFetchState<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
}

interface UseFetchOptions {
  immediate?: boolean;
}

export function useFetch<T>(
  url: string,
  options?: RequestInit & UseFetchOptions
) {
  const [state, setState] = useState<UseFetchState<T>>({
    data: null,
    loading: options?.immediate !== false,
    error: null,
  });

  const execute = useCallback(async () => {
    setState(prev => ({ ...prev, loading: true, error: null }));

    try {
      const response = await fetch(url, options);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      const data = await response.json();
      setState({ data, loading: false, error: null });
      return data;
    } catch (error) {
      setState(prev => ({
        ...prev,
        loading: false,
        error: error as Error,
      }));
      throw error;
    }
  }, [url, options]);

  useEffect(() => {
    if (options?.immediate !== false) {
      execute();
    }
  }, [execute, options?.immediate]);

  return { ...state, refetch: execute };
}

// Usage
function UserProfile({ id }: { id: string }) {
  const { data: user, loading, error, refetch } = useFetch<User>(
    `/api/users/${id}`
  );

  if (loading) return <Spinner />;
  if (error) return <Error message={error.message} onRetry={refetch} />;
  return <div>{user?.name}</div>;
}

typescript
import { useState, useEffect, useCallback } from 'react';

interface UseFetchState<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
}

interface UseFetchOptions {
  immediate?: boolean;
}

export function useFetch<T>(
  url: string,
  options?: RequestInit & UseFetchOptions
) {
  const [state, setState] = useState<UseFetchState<T>>({
    data: null,
    loading: options?.immediate !== false,
    error: null,
  });

  const execute = useCallback(async () => {
    setState(prev => ({ ...prev, loading: true, error: null }));

    try {
      const response = await fetch(url, options);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      const data = await response.json();
      setState({ data, loading: false, error: null });
      return data;
    } catch (error) {
      setState(prev => ({
        ...prev,
        loading: false,
        error: error as Error,
      }));
      throw error;
    }
  }, [url, options]);

  useEffect(() => {
    if (options?.immediate !== false) {
      execute();
    }
  }, [execute, options?.immediate]);

  return { ...state, refetch: execute };
}

// Usage
function UserProfile({ id }: { id: string }) {
  const { data: user, loading, error, refetch } = useFetch<User>(
    `/api/users/${id}`
  );

  if (loading) return <Spinner />;
  if (error) return <Error message={error.message} onRetry={refetch} />;
  return <div>{user?.name}</div>;
}

SWR (Stale-While-Revalidate)

SWR(Stale-While-Revalidate)

bash
npm install swr
bash
npm install swr

Basic Usage

基础用法

typescript
import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then(res => res.json());

function UserProfile({ id }: { id: string }) {
  const { data, error, isLoading, mutate } = useSWR<User>(
    `/api/users/${id}`,
    fetcher
  );

  if (isLoading) return <Spinner />;
  if (error) return <Error />;
  return <div>{data?.name}</div>;
}
typescript
import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then(res => res.json());

function UserProfile({ id }: { id: string }) {
  const { data, error, isLoading, mutate } = useSWR<User>(
    `/api/users/${id}`,
    fetcher
  );

  if (isLoading) return <Spinner />;
  if (error) return <Error />;
  return <div>{data?.name}</div>;
}

Global Configuration

全局配置

typescript
import { SWRConfig } from 'swr';

const fetcher = async (url: string) => {
  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${getToken()}` },
  });
  if (!res.ok) throw new Error('API Error');
  return res.json();
};

function App() {
  return (
    <SWRConfig
      value={{
        fetcher,
        revalidateOnFocus: true,
        revalidateOnReconnect: true,
        dedupingInterval: 2000,
        errorRetryCount: 3,
        onError: (error) => console.error(error),
      }}
    >
      <MyApp />
    </SWRConfig>
  );
}
typescript
import { SWRConfig } from 'swr';

const fetcher = async (url: string) => {
  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${getToken()}` },
  });
  if (!res.ok) throw new Error('API Error');
  return res.json();
};

function App() {
  return (
    <SWRConfig
      value={{
        fetcher,
        revalidateOnFocus: true,
        revalidateOnReconnect: true,
        dedupingInterval: 2000,
        errorRetryCount: 3,
        onError: (error) => console.error(error),
      }}
    >
      <MyApp />
    </SWRConfig>
  );
}

Mutations with useSWRMutation

结合useSWRMutation实现数据变更

typescript
import useSWRMutation from 'swr/mutation';

async function createUser(url: string, { arg }: { arg: CreateUserDto }) {
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(arg),
  });
  return res.json();
}

function CreateUserForm() {
  const { trigger, isMutating } = useSWRMutation('/api/users', createUser);

  const handleSubmit = async (data: CreateUserDto) => {
    try {
      await trigger(data);
      // Success
    } catch (error) {
      // Error
    }
  };

  return <form onSubmit={handleSubmit}>...</form>;
}
typescript
import useSWRMutation from 'swr/mutation';

async function createUser(url: string, { arg }: { arg: CreateUserDto }) {
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(arg),
  });
  return res.json();
}

function CreateUserForm() {
  const { trigger, isMutating } = useSWRMutation('/api/users', createUser);

  const handleSubmit = async (data: CreateUserDto) => {
    try {
      await trigger(data);
      // 成功处理
    } catch (error) {
      // 错误处理
    }
  };

  return <form onSubmit={handleSubmit}>...</form>;
}

Optimistic Updates

乐观更新

typescript
function UserList() {
  const { data, mutate } = useSWR<User[]>('/api/users', fetcher);

  const deleteUser = async (id: string) => {
    // Optimistic update
    const optimisticData = data?.filter(u => u.id !== id);
    mutate(optimisticData, { revalidate: false });

    try {
      await fetch(`/api/users/${id}`, { method: 'DELETE' });
      mutate(); // Revalidate after success
    } catch {
      mutate(data); // Rollback on error
    }
  };

  return (
    <ul>
      {data?.map(user => (
        <li key={user.id}>
          {user.name}
          <button onClick={() => deleteUser(user.id)}>Delete</button>
        </li>
      ))}
    </ul>
  );
}

typescript
function UserList() {
  const { data, mutate } = useSWR<User[]>('/api/users', fetcher);

  const deleteUser = async (id: string) => {
    // 乐观更新
    const optimisticData = data?.filter(u => u.id !== id);
    mutate(optimisticData, { revalidate: false });

    try {
      await fetch(`/api/users/${id}`, { method: 'DELETE' });
      mutate(); // 成功后重新验证
    } catch {
      mutate(data); // 失败后回滚
    }
  };

  return (
    <ul>
      {data?.map(user => (
        <li key={user.id}>
          {user.name}
          <button onClick={() => deleteUser(user.id)}>删除</button>
        </li>
      ))}
    </ul>
  );
}

React Suspense with use()

结合React Suspense与use() Hook

typescript
// React 18+ with use() hook
import { use, Suspense } from 'react';

// Create promise outside component
const userPromise = fetch('/api/users/1').then(res => res.json());

function UserProfile() {
  const user = use(userPromise);
  return <div>{user.name}</div>;
}

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <UserProfile />
    </Suspense>
  );
}
typescript
// React 18+ 及以上版本支持use() hook
import { use, Suspense } from 'react';

// 在组件外部创建Promise
const userPromise = fetch('/api/users/1').then(res => res.json());

function UserProfile() {
  const user = use(userPromise);
  return <div>{user.name}</div>;
}

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <UserProfile />
    </Suspense>
  );
}

Suspense with SWR

结合SWR使用Suspense

typescript
import useSWR from 'swr';

function UserProfile({ id }: { id: string }) {
  const { data } = useSWR<User>(`/api/users/${id}`, fetcher, {
    suspense: true,
  });

  // data is guaranteed to exist (no loading state)
  return <div>{data.name}</div>;
}

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <UserProfile id="1" />
    </Suspense>
  );
}

typescript
import useSWR from 'swr';

function UserProfile({ id }: { id: string }) {
  const { data } = useSWR<User>(`/api/users/${id}`, fetcher, {
    suspense: true,
  });

  // data 已确保存在(无需处理加载状态)
  return <div>{data.name}</div>;
}

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <UserProfile id="1" />
    </Suspense>
  );
}

Error Boundaries

错误边界

typescript
import { Component, ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
  onError?: (error: Error) => void;
}

interface State {
  hasError: boolean;
  error: Error | null;
}

export class ApiErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false, error: null };

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error) {
    this.props.onError?.(error);
  }

  reset = () => {
    this.setState({ hasError: false, error: null });
  };

  render() {
    if (this.state.hasError) {
      return this.props.fallback ?? (
        <div>
          <h2>Something went wrong</h2>
          <button onClick={this.reset}>Try again</button>
        </div>
      );
    }
    return this.props.children;
  }
}

// Usage
function App() {
  return (
    <ApiErrorBoundary
      fallback={<ErrorFallback />}
      onError={(error) => logError(error)}
    >
      <Suspense fallback={<Spinner />}>
        <UserProfile />
      </Suspense>
    </ApiErrorBoundary>
  );
}

typescript
import { Component, ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
  onError?: (error: Error) => void;
}

interface State {
  hasError: boolean;
  error: Error | null;
}

export class ApiErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false, error: null };

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error) {
    this.props.onError?.(error);
  }

  reset = () => {
    this.setState({ hasError: false, error: null });
  };

  render() {
    if (this.state.hasError) {
      return this.props.fallback ?? (
        <div>
          <h2>出现错误</h2>
          <button onClick={this.reset}>重试</button>
        </div>
      );
    }
    return this.props.children;
  }
}

// 用法
function App() {
  return (
    <ApiErrorBoundary
      fallback={<ErrorFallback />}
      onError={(error) => logError(error)}
    >
      <Suspense fallback={<Spinner />}>
        <UserProfile />
      </Suspense>
    </ApiErrorBoundary>
  );
}

Real-Time Updates

实时更新

Server-Sent Events (SSE)

服务器发送事件(SSE)

typescript
function useSSE<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    const eventSource = new EventSource(url);

    eventSource.onmessage = (event) => {
      setData(JSON.parse(event.data));
    };

    eventSource.onerror = () => {
      setError(new Error('SSE connection failed'));
      eventSource.close();
    };

    return () => eventSource.close();
  }, [url]);

  return { data, error };
}

// Usage
function Notifications() {
  const { data: notification } = useSSE<Notification>('/api/notifications');
  return notification ? <Toast message={notification.message} /> : null;
}
typescript
function useSSE<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    const eventSource = new EventSource(url);

    eventSource.onmessage = (event) => {
      setData(JSON.parse(event.data));
    };

    eventSource.onerror = () => {
      setError(new Error('SSE连接失败'));
      eventSource.close();
    };

    return () => eventSource.close();
  }, [url]);

  return { data, error };
}

// 用法
function Notifications() {
  const { data: notification } = useSSE<Notification>('/api/notifications');
  return notification ? <Toast message={notification.message} /> : null;
}

WebSocket Hook

WebSocket Hook

typescript
function useWebSocket<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [isConnected, setIsConnected] = useState(false);
  const wsRef = useRef<WebSocket | null>(null);

  useEffect(() => {
    const ws = new WebSocket(url);
    wsRef.current = ws;

    ws.onopen = () => setIsConnected(true);
    ws.onclose = () => setIsConnected(false);
    ws.onmessage = (event) => setData(JSON.parse(event.data));

    return () => ws.close();
  }, [url]);

  const send = useCallback((message: unknown) => {
    wsRef.current?.send(JSON.stringify(message));
  }, []);

  return { data, isConnected, send };
}

// Usage
function Chat() {
  const { data: message, send, isConnected } = useWebSocket<Message>(
    'wss://api.example.com/chat'
  );

  const handleSend = (text: string) => {
    send({ type: 'message', text });
  };

  return (
    <div>
      {isConnected ? 'Connected' : 'Connecting...'}
      {message && <Message data={message} />}
    </div>
  );
}

typescript
function useWebSocket<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [isConnected, setIsConnected] = useState(false);
  const wsRef = useRef<WebSocket | null>(null);

  useEffect(() => {
    const ws = new WebSocket(url);
    wsRef.current = ws;

    ws.onopen = () => setIsConnected(true);
    ws.onclose = () => setIsConnected(false);
    ws.onmessage = (event) => setData(JSON.parse(event.data));

    return () => ws.close();
  }, [url]);

  const send = useCallback((message: unknown) => {
    wsRef.current?.send(JSON.stringify(message));
  }, []);

  return { data, isConnected, send };
}

// 用法
function Chat() {
  const { data: message, send, isConnected } = useWebSocket<Message>(
    'wss://api.example.com/chat'
  );

  const handleSend = (text: string) => {
    send({ type: 'message', text });
  };

  return (
    <div>
      {isConnected ? '已连接' : '连接中...'}
      {message && <Message data={message} />}
    </div>
  );
}

Loading States

加载状态

Skeleton Loading

骨架屏加载

typescript
function UserCardSkeleton() {
  return (
    <div className="animate-pulse">
      <div className="h-12 w-12 bg-gray-200 rounded-full" />
      <div className="h-4 w-32 bg-gray-200 mt-2 rounded" />
      <div className="h-3 w-24 bg-gray-200 mt-1 rounded" />
    </div>
  );
}

function UserCard({ id }: { id: string }) {
  const { data, isLoading } = useSWR<User>(`/api/users/${id}`, fetcher);

  if (isLoading) return <UserCardSkeleton />;
  return (
    <div>
      <img src={data?.avatar} />
      <h3>{data?.name}</h3>
      <p>{data?.email}</p>
    </div>
  );
}

typescript
function UserCardSkeleton() {
  return (
    <div className="animate-pulse">
      <div className="h-12 w-12 bg-gray-200 rounded-full" />
      <div className="h-4 w-32 bg-gray-200 mt-2 rounded" />
      <div className="h-3 w-24 bg-gray-200 mt-1 rounded" />
    </div>
  );
}

function UserCard({ id }: { id: string }) {
  const { data, isLoading } = useSWR<User>(`/api/users/${id}`, fetcher);

  if (isLoading) return <UserCardSkeleton />;
  return (
    <div>
      <img src={data?.avatar} />
      <h3>{data?.name}</h3>
      <p>{data?.email}</p>
    </div>
  );
}

Anti-Patterns

反模式

Anti-PatternWhy It's BadCorrect Approach
Fetching in useEffect without cleanupRace conditionsUse AbortController
Not handling loading/error statesPoor UXAlways show loading/error UI
Fetching in renderInfinite loopsUse useEffect or Suspense
No request deduplicationDuplicate requestsUse SWR or TanStack Query
Hardcoded API URLsHard to maintainUse environment variables
Not retrying failed requestsPoor resilienceAdd retry logic
反模式问题所在正确做法
在useEffect中获取数据但未清理可能出现竞态条件使用AbortController
未处理加载/错误状态用户体验差始终展示加载/错误UI
在渲染阶段获取数据无限循环使用useEffect或Suspense
未实现请求去重重复请求使用SWR或TanStack Query
硬编码API地址难以维护使用环境变量
不对失败请求进行重试鲁棒性差添加重试逻辑

Quick Troubleshooting

快速故障排查

IssueLikely CauseSolution
Infinite fetch loopFetching in renderMove to useEffect
Stale data shownNo revalidationConfigure SWR revalidation
Race conditionMultiple rapid requestsUse AbortController
Memory leak warningUpdating unmounted componentCheck mounted state or cleanup
Duplicate requestsNo deduplicationUse SWR or caching layer
CORS errorBackend not configuredConfigure CORS headers
问题可能原因解决方案
无限获取循环在渲染阶段获取数据移至useEffect中
展示过期数据未配置重新验证配置SWR的重新验证规则
竞态条件频繁的连续请求使用AbortController
内存泄漏警告更新已卸载组件的状态检查组件挂载状态或添加清理逻辑
重复请求未实现去重使用SWR或缓存层
CORS错误后端未配置配置CORS响应头

Production Readiness

生产环境就绪检查

Checklist

检查清单

  • Global fetcher with auth headers
  • Error boundary for API errors
  • Loading skeletons for better UX
  • Optimistic updates for mutations
  • Request deduplication
  • Automatic retry on failure
  • Revalidation strategy defined
  • Real-time updates where needed
  • Proper cleanup on unmount
  • 配置全局fetcher并携带认证头
  • 为API错误添加错误边界
  • 使用骨架屏提升用户体验
  • 为数据变更实现乐观更新
  • 实现请求去重
  • 配置失败请求自动重试
  • 定义重新验证策略
  • 在需要的地方实现实时更新
  • 在组件卸载时进行正确清理

Reference Documentation

参考文档

Deep Knowledge: Use
mcp__documentation__fetch_docs
with technology:
react
for comprehensive documentation.
  • Custom Hooks
  • Suspense
  • Error Boundaries
  • SWR
  • Real-Time
深度参考:使用
mcp__documentation__fetch_docs
工具并指定技术为
react
,可获取完整文档。
  • 自定义Hooks
  • Suspense
  • 错误边界
  • SWR
  • 实时更新