📖 核心概念
- RESTful API设计原则
- HTTP方法与状态码
- Fetch API高级用法
- 请求拦截与错误处理
- API客户端封装
- GraphQL简介
💻 代码实现
API客户端封装
✅ typescript
// 类型安全的API客户端
class ApiClient {
private baseUrl: string;
private token: string | null = null;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
setToken(token: string) { this.token = token; }
private async request<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = \`\${this.baseUrl}\${endpoint}\`;
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...options.headers as Record<string, string>,
};
if (this.token) {
headers['Authorization'] = \`Bearer \${this.token}\`;
}
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
try {
const response = await fetch(url, {
...options,
headers,
signal: controller.signal,
});
clearTimeout(timeout);
if (!response.ok) {
const error = await response.json().catch(() => ({ message: response.statusText }));
throw new ApiError(response.status, error.message || response.statusText, error);
}
if (response.status === 204) return null as T;
return response.json();
} catch (error) {
clearTimeout(timeout);
if (error instanceof DOMException && error.name === 'AbortError') {
throw new ApiError(408, '请求超时', {});
}
throw error;
}
}
get<T>(endpoint: string) { return this.request<T>(endpoint); }
post<T>(endpoint: string, data: unknown) {
return this.request<T>(endpoint, { method: 'POST', body: JSON.stringify(data) });
}
put<T>(endpoint: string, data: unknown) {
return this.request<T>(endpoint, { method: 'PUT', body: JSON.stringify(data) });
}
patch<T>(endpoint: string, data: unknown) {
return this.request<T>(endpoint, { method: 'PATCH', body: JSON.stringify(data) });
}
delete<T>(endpoint: string) { return this.request<T>(endpoint, { method: 'DELETE' }); }
}
class ApiError extends Error {
constructor(public status: number, message: string, public data: unknown) {
super(message);
this.name = 'ApiError';
}
}
// API类型定义
interface User { id: string; name: string; email: string; }
interface Post { id: string; title: string; content: string; author: User; }
interface PaginatedResponse<T> { data: T[]; total: number; page: number; pageSize: number; }
// 使用
const api = new ApiClient('/api');
api.setToken(localStorage.getItem('token') || '');
const users = await api.get<PaginatedResponse<User>>('/users?page=1&pageSize=10');
const post = await api.post<Post>('/posts', { title: 'Hello', content: 'World' });
React Query数据管理
✅ tsx
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// 数据获取Hook
function useUsers(page: number) {
return useQuery({
queryKey: ['users', page],
queryFn: () => api.get<PaginatedResponse<User>>(\`/users?page=\${page}\`),
staleTime: 5 * 60 * 1000, // 5分钟内不重新请求
placeholderData: (prev) => prev, // 切换页面时保留旧数据
});
}
// 创建mutation
function useCreatePost() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreatePostInput) => api.post<Post>('/posts', data),
onSuccess: (newPost) => {
// 乐观更新:直接修改缓存
queryClient.setQueryData(['posts'], (old: Post[] | undefined) =>
old ? [newPost, ...old] : [newPost]
);
// 或者让相关查询失效
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
}
// 乐观更新
function useUpdatePost() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: UpdatePostInput) => api.patch<Post>(\`/posts/\${data.id}\`, data),
onMutate: async (newData) => {
await queryClient.cancelQueries({ queryKey: ['posts', newData.id] });
const previousPost = queryClient.getQueryData(['posts', newData.id]);
queryClient.setQueryData(['posts', newData.id], (old: Post) => ({
...old, ...newData,
}));
return { previousPost };
},
onError: (err, newData, context) => {
queryClient.setQueryData(['posts', newData.id], context?.previousPost);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
}
// 组件中使用
function UserList() {
const [page, setPage] = useState(1);
const { data, isLoading, error } = useUsers(page);
if (isLoading) return <Skeleton />;
if (error) return <Error message={error.message} />;
return (
<div>
{data.data.map(user => <UserCard key={user.id} user={user} />)}
<Pagination current={page} total={Math.ceil(data.total / data.pageSize)} onChange={setPage} />
</div>
);
}
🎯 练习任务
- 1 封装一个类型安全的API客户端,支持拦截器和重试
- 2 使用React Query管理API数据,实现乐观更新
- 3 设计一个RESTful API,包含分页、过滤、排序
- 4 实现无限滚动列表的数据加载
🏆 成就解锁
API架构师 — 掌握前后端通信设计,构建高效的API交互层