react-api
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact API Patterns Core Knowledge
React API调用模式核心知识
Deep Knowledge: Usewith technology:mcp__documentation__fetch_docsfor comprehensive documentation.react
深度参考:使用工具并指定技术为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 swrbash
npm install swrBasic 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-Pattern | Why It's Bad | Correct Approach |
|---|---|---|
| Fetching in useEffect without cleanup | Race conditions | Use AbortController |
| Not handling loading/error states | Poor UX | Always show loading/error UI |
| Fetching in render | Infinite loops | Use useEffect or Suspense |
| No request deduplication | Duplicate requests | Use SWR or TanStack Query |
| Hardcoded API URLs | Hard to maintain | Use environment variables |
| Not retrying failed requests | Poor resilience | Add retry logic |
| 反模式 | 问题所在 | 正确做法 |
|---|---|---|
| 在useEffect中获取数据但未清理 | 可能出现竞态条件 | 使用AbortController |
| 未处理加载/错误状态 | 用户体验差 | 始终展示加载/错误UI |
| 在渲染阶段获取数据 | 无限循环 | 使用useEffect或Suspense |
| 未实现请求去重 | 重复请求 | 使用SWR或TanStack Query |
| 硬编码API地址 | 难以维护 | 使用环境变量 |
| 不对失败请求进行重试 | 鲁棒性差 | 添加重试逻辑 |
Quick Troubleshooting
快速故障排查
| Issue | Likely Cause | Solution |
|---|---|---|
| Infinite fetch loop | Fetching in render | Move to useEffect |
| Stale data shown | No revalidation | Configure SWR revalidation |
| Race condition | Multiple rapid requests | Use AbortController |
| Memory leak warning | Updating unmounted component | Check mounted state or cleanup |
| Duplicate requests | No deduplication | Use SWR or caching layer |
| CORS error | Backend not configured | Configure 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: Usewith technology:mcp__documentation__fetch_docsfor comprehensive documentation.react
- Custom Hooks
- Suspense
- Error Boundaries
- SWR
- Real-Time
深度参考:使用工具并指定技术为mcp__documentation__fetch_docs,可获取完整文档。react
- 自定义Hooks
- Suspense
- 错误边界
- SWR
- 实时更新