Appearance
各方案优缺点
概述
React应用中有多种数据获取方案,每种方案都有其优缺点和适用场景。本文将全面对比Fetch API、Axios、SWR、TanStack Query、GraphQL、tRPC等方案,帮助你选择最适合项目的数据获取策略。
Fetch API
优点
原生支持
- 无需安装依赖
- 浏览器原生API
- 现代浏览器全面支持
Promise based
- 支持async/await
- 链式调用
- 易于理解
灵活性高
- 完全控制请求配置
- 可自定义封装
- 适合简单场景
缺点
功能有限
- 不支持请求取消(需AbortController)
- 不支持上传进度
- 不支持超时配置
- 不自动转换JSON
错误处理复杂
- HTTP错误不会reject
- 需要手动检查response.ok
- 错误处理代码冗余
无状态管理
- 需手动管理loading、error
- 无缓存机制
- 无重试机制
示例对比
jsx
// 问题: 需要大量样板代码
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUser = async () => {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('Failed to fetch');
}
const data = await response.json();
setUser(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{user?.name}</div>;
}适用场景
- 简单的API调用
- 原型开发
- 不需要高级功能的小项目
- 需要完全控制的场景
Axios
优点
丰富的功能
- 自动转换JSON
- 请求/响应拦截器
- 请求取消
- 超时配置
- 上传/下载进度
良好的错误处理
- HTTP错误自动reject
- 统一的错误格式
- 易于集中处理
浏览器兼容性
- 支持旧浏览器
- 同时支持浏览器和Node.js
开发体验好
- API设计优雅
- 文档完善
- 社区活跃
缺点
包体积
- 比Fetch API大
- 增加bundle size
仍需状态管理
- 需手动管理loading/error
- 无内置缓存
- 无自动重新获取
配置复杂性
- 拦截器配置可能复杂
- 需要额外封装
示例对比
jsx
// 改进: 拦截器统一处理
const apiClient = axios.create({
baseURL: '/api',
timeout: 10000,
});
apiClient.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${getToken()}`;
return config;
});
apiClient.interceptors.response.use(
response => response.data,
error => {
if (error.response?.status === 401) {
window.location.href = '/login';
}
return Promise.reject(error);
}
);
// 但仍需手动管理状态
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
apiClient.get(`/users/${userId}`)
.then(setUser)
.finally(() => setLoading(false));
}, [userId]);
return loading ? <div>Loading...</div> : <div>{user?.name}</div>;
}适用场景
- 需要高级HTTP功能
- 需要支持旧浏览器
- RESTful API应用
- 不需要复杂缓存策略
SWR
优点
简洁的API
- 一行代码完成数据获取
- 自动状态管理
- 声明式用法
智能缓存
- Stale-While-Revalidate策略
- 自动去重
- 聚焦重新验证
实时体验
- 自动重新验证
- 乐观更新
- 后台数据更新
轻量级
- 包体积小(~5KB)
- 无外部依赖
- 性能优秀
缺点
功能相对有限
- 主要针对GET请求
- Mutation功能较弱
- 无内置分页支持(需useSWRInfinite)
文档和生态
- 相比React Query文档较少
- 插件和工具较少
- 社区规模较小
类型支持
- TypeScript支持一般
- 需要手动定义类型
示例对比
jsx
// 优势: 极简的API
import useSWR from 'swr';
function UserProfile({ userId }) {
const { data: user, error, isLoading } = useSWR(
`/api/users/${userId}`,
fetcher
);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error</div>;
return <div>{user.name}</div>;
}
// 劣势: Mutation需额外处理
function CreateUser() {
const { mutate } = useSWRConfig();
const handleCreate = async (data) => {
await fetch('/api/users', { method: 'POST', body: JSON.stringify(data) });
mutate('/api/users'); // 手动触发revalidate
};
return <button onClick={handleCreate}>Create</button>;
}适用场景
- 数据展示为主的应用
- 需要实时数据更新
- 重视包体积
- 简单的数据获取场景
TanStack Query (React Query)
优点
功能最全面
- 完整的查询和变更支持
- 强大的缓存管理
- 分页和无限滚动
- 离线支持
- DevTools
开发体验极佳
- 声明式API
- 自动状态管理
- 完善的TypeScript支持
- 优秀的文档
高度可配置
- 灵活的缓存策略
- 重试机制
- 乐观更新
- 查询失效
生态系统强大
- 活跃的社区
- 丰富的插件
- 持续维护
缺点
学习曲线
- 概念较多
- 配置选项复杂
- 需要时间熟悉
包体积
- 相对较大(~13KB)
- 功能多导致体积大
过度工程
- 简单项目可能过于复杂
- 配置可能冗余
示例对比
jsx
// 优势: 功能完整
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function UserList() {
const queryClient = useQueryClient();
// 查询
const { data: users } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 5000,
});
// 变更
const { mutate: createUser } = useMutation({
mutationFn: createUserAPI,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
// 无限滚动
const {
data,
fetchNextPage,
hasNextPage,
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
getNextPageParam: (lastPage) => lastPage.nextCursor,
});
return <div>...</div>;
}适用场景
- 中大型应用
- 数据密集型应用
- 需要复杂缓存策略
- 需要完整的数据同步方案
GraphQL (Apollo Client)
优点
精确数据获取
- 只请求需要的字段
- 避免over-fetching
- 减少网络传输
强大的类型系统
- Schema定义
- 代码生成
- 完整的类型安全
高级功能
- 实时订阅
- 缓存归一化
- 乐观更新
- 分页
开发体验
- GraphQL Playground
- Apollo DevTools
- 代码补全
缺点
学习曲线陡峭
- 需要学习GraphQL
- Schema设计复杂
- 查询语法特殊
复杂度高
- 服务端配置复杂
- 需要额外的基础设施
- N+1查询问题
包体积大
- Apollo Client体积较大(~33KB)
- 依赖较多
调试困难
- 错误信息不直观
- 网络请求难以追踪
示例对比
jsx
// 优势: 精确查询
import { useQuery, gql } from '@apollo/client';
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
posts {
id
title
}
}
}
`;
function UserProfile({ userId }) {
const { data, loading } = useQuery(GET_USER, {
variables: { id: userId },
});
return <div>{data?.user.name}</div>;
}
// 劣势: 配置复杂
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
posts: {
merge(existing, incoming) {
return [...existing, ...incoming];
},
},
},
},
},
}),
});适用场景
- 复杂的数据关系
- 移动应用(减少数据传输)
- 多客户端应用
- 需要实时功能
- 团队熟悉GraphQL
tRPC
优点
端到端类型安全
- 无需代码生成
- TypeScript原生支持
- 自动类型推断
开发体验最佳
- 自动补全
- 重构友好
- 类型错误编译时发现
极简API
- 学习成本低
- 代码量少
- 直观易懂
轻量级
- 核心包很小(~2KB)
- 无额外依赖
缺点
TypeScript only
- 必须使用TypeScript
- 不支持其他语言客户端
全栈TypeScript限制
- 前后端必须共享类型
- Monorepo或类型共享配置
功能相对简单
- 不如GraphQL灵活
- 查询语言不够表达力
生态系统新
- 工具和插件较少
- 社区相对小
示例对比
jsx
// 优势: 类型安全
import { trpc } from './utils/trpc';
function UserProfile({ userId }: { userId: string }) {
// 完全类型安全,自动补全
const { data: user } = trpc.user.getUser.useQuery({ id: userId });
const { mutate } = trpc.user.update.useMutation();
// TypeScript会检查参数类型
const handleUpdate = () => {
mutate({
id: userId,
name: 'New Name', // 类型检查
});
};
return <div>{user?.name}</div>;
}
// 劣势: 仅限TypeScript全栈
// 服务端
export const userRouter = router({
getUser: publicProcedure
.input(z.object({ id: z.string() }))
.query(({ input }) => {
return db.user.findUnique({ where: { id: input.id } });
}),
});适用场景
- TypeScript全栈项目
- 内部工具/管理系统
- 需要极致类型安全
- 团队全部使用TypeScript
- 新项目快速开发
方案对比总结表
| 特性 | Fetch | Axios | SWR | React Query | GraphQL | tRPC |
|---|---|---|---|---|---|---|
| 包体积 | 0KB | ~13KB | ~5KB | ~13KB | ~33KB | ~2KB |
| 学习曲线 | 低 | 低 | 中 | 中 | 高 | 低 |
| 类型安全 | 弱 | 弱 | 中 | 强 | 强 | 极强 |
| 状态管理 | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ |
| 缓存 | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ |
| 乐观更新 | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ |
| 无限滚动 | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ |
| 实时订阅 | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ |
| DevTools | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ |
| 文档质量 | 一般 | 好 | 好 | 极好 | 极好 | 好 |
| 社区规模 | - | 大 | 中 | 大 | 大 | 小 |
| 适合场景 | 简单 | 通用 | 实时 | 复杂 | 关系数据 | TS全栈 |
选择建议
按项目规模选择
小型项目 (< 10个页面)
- Fetch API + 自定义Hook
- SWR
中型项目 (10-50个页面)
- Axios + SWR
- TanStack Query
大型项目 (> 50个页面)
- TanStack Query
- GraphQL (Apollo Client)
- tRPC (TypeScript全栈)
按团队技术栈选择
JavaScript团队
- Axios
- SWR
- React Query
TypeScript团队
- React Query
- tRPC
- GraphQL + Codegen
全栈团队
- tRPC
- GraphQL
按应用类型选择
数据展示应用
- SWR
- React Query
数据密集应用
- React Query
- GraphQL
实时应用
- GraphQL (Subscriptions)
- tRPC (WebSocket)
移动应用
- GraphQL (减少数据传输)
- React Query
总结
选择数据获取方案的关键因素:
- 项目规模和复杂度
- 团队技术栈和经验
- 性能和包体积要求
- 类型安全需求
- 实时功能需求
- 开发效率要求
没有绝对最好的方案,只有最适合的方案。根据项目实际需求选择合适的工具,有时候组合使用多种方案也是不错的选择。
深入对比分析
性能对比
初始加载性能
tsx
// 1. Bundle Size影响
/*
Fetch API: 0KB (原生)
tRPC: ~2KB (最小)
SWR: ~5KB (轻量)
Axios: ~13KB (适中)
React Query:~13KB (适中)
GraphQL: ~33KB (最大)
*/
// 2. 首次渲染时间对比
// Fetch API - 最快但需要手动处理
function FetchExample() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/users').then(res => res.json()).then(setData);
}, []);
// 首次渲染: ~50ms
}
// SWR - 极快
function SWRExample() {
const { data } = useSWR('/api/users', fetcher);
// 首次渲染: ~60ms
}
// React Query - 稍慢但功能完整
function RQExample() {
const { data } = useQuery({ queryKey: ['users'], queryFn: fetchUsers });
// 首次渲染: ~80ms
}
// GraphQL - 最慢但数据精确
function GraphQLExample() {
const { data } = useQuery(GET_USERS);
// 首次渲染: ~120ms
}运行时性能
tsx
// 1. 重渲染次数对比
function PerformanceComparison() {
// Fetch - 手动控制,最少重渲染
const [users, setUsers] = useState([]);
// 重渲染: 2次 (loading → data)
// SWR - 自动优化
const { data } = useSWR('/api/users', fetcher);
// 重渲染: 2-3次 (loading → stale → fresh)
// React Query - 可配置
const { data } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
notifyOnChangeProps: ['data', 'error'], // 减少重渲染
});
// 重渲染: 2-4次
// GraphQL - 归一化缓存
const { data } = useQuery(GET_USERS);
// 重渲染: 2-3次
}
// 2. 内存占用对比
class MemoryUsage {
// Fetch - 最小
fetch = 0.1; // MB
// SWR - 较小
swr = 0.5; // MB (带缓存)
// React Query - 中等
reactQuery = 1.2; // MB (完整缓存+DevTools)
// GraphQL - 较大
graphql = 2.5; // MB (归一化缓存)
// tRPC - 最小
trpc = 0.3; // MB
}开发效率对比
tsx
// 1. 代码量对比 - 实现用户列表CRUD
// === Fetch API: ~200 lines ===
function UserCRUDWithFetch() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchUsers = async () => {
setLoading(true);
try {
const res = await fetch('/api/users');
if (!res.ok) throw new Error('Failed');
const data = await res.json();
setUsers(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const createUser = async (user) => {
try {
await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user),
});
fetchUsers(); // 重新获取
} catch (err) {
setError(err.message);
}
};
// ... update, delete 也需类似代码
useEffect(() => { fetchUsers(); }, []);
return <>...</>;
}
// === SWR: ~80 lines ===
function UserCRUDWithSWR() {
const { data: users, error, mutate } = useSWR('/api/users', fetcher);
const createUser = async (user) => {
await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user),
});
mutate(); // 自动重新验证
};
const updateUser = async (id, data) => {
await fetch(`/api/users/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
mutate();
};
return <>...</>;
}
// === React Query: ~60 lines ===
function UserCRUDWithRQ() {
const { data: users } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
const { mutate: createUser } = useMutation({
mutationFn: createUserAPI,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
const { mutate: updateUser } = useMutation({
mutationFn: ({ id, data }) => updateUserAPI(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
return <>...</>;
}
// === GraphQL: ~40 lines ===
function UserCRUDWithGraphQL() {
const { data } = useQuery(GET_USERS);
const [createUser] = useMutation(CREATE_USER, {
refetchQueries: [{ query: GET_USERS }],
});
const [updateUser] = useMutation(UPDATE_USER, {
refetchQueries: [{ query: GET_USERS }],
});
return <>...</>;
}
// === tRPC: ~30 lines ===
function UserCRUDWithTRPC() {
const { data: users } = trpc.user.getAll.useQuery();
const { mutate: createUser } = trpc.user.create.useMutation({
onSuccess: () => utils.user.getAll.invalidate(),
});
const { mutate: updateUser } = trpc.user.update.useMutation({
onSuccess: () => utils.user.getAll.invalidate(),
});
return <>...</>;
}类型安全对比
tsx
// 1. TypeScript支持程度
// === Fetch API: 弱类型 ===
async function fetchUser(id: string) {
const response = await fetch(`/api/users/${id}`);
const data = await response.json(); // type: any
return data; // 需手动类型断言
}
// === Axios: 泛型支持 ===
async function fetchUser(id: string) {
const { data } = await axios.get<User>(`/api/users/${id}`);
return data; // type: User (手动指定)
}
// === React Query: 强类型 ===
function useUser(id: string) {
return useQuery({
queryKey: ['user', id],
queryFn: async (): Promise<User> => {
const { data } = await axios.get<User>(`/api/users/${id}`);
return data;
},
});
// data: User | undefined (自动推断)
}
// === GraphQL + Codegen: 极强 ===
// 自动生成类型
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;
function useUser(id: string) {
const { data } = useQuery(GET_USER, { variables: { id } });
// data: GetUserQuery (自动生成)
// data.user.name - 完全类型安全
}
// === tRPC: 最强 ===
// 服务端定义
const userRouter = router({
getUser: publicProcedure
.input(z.object({ id: z.string() }))
.query(({ input }) => db.user.findUnique({ where: { id: input.id } })),
});
// 客户端 - 完全自动
function useUser(id: string) {
const { data } = trpc.user.getUser.useQuery({ id });
// data: User (自动推断,无需任何类型注解)
// 重构时自动同步
}错误处理对比
tsx
// 1. 错误处理复杂度
// === Fetch: 最复杂 ===
async function fetchWithFetch() {
try {
const res = await fetch('/api/users');
if (!res.ok) {
if (res.status === 404) throw new Error('Not found');
if (res.status === 401) throw new Error('Unauthorized');
throw new Error('Request failed');
}
const data = await res.json();
return data;
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request aborted');
} else if (error.name === 'TypeError') {
console.log('Network error');
}
throw error;
}
}
// === Axios: 简化 ===
axios.interceptors.response.use(
response => response,
error => {
if (error.response) {
// 服务器响应错误
const status = error.response.status;
if (status === 401) handleUnauthorized();
if (status === 404) handleNotFound();
} else if (error.request) {
// 请求发出但无响应
handleNetworkError();
} else {
// 请求配置错误
handleRequestError();
}
return Promise.reject(error);
}
);
// === SWR: 集成错误状态 ===
function Component() {
const { data, error, isLoading } = useSWR('/api/users', fetcher, {
onError: (error) => {
if (error.status === 401) redirect('/login');
},
onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
if (error.status === 404) return; // 不重试404
if (retryCount >= 3) return; // 最多3次
setTimeout(() => revalidate({ retryCount }), 5000);
},
});
if (error) return <ErrorComponent error={error} />;
return <div>{data}</div>;
}
// === React Query: 最完善 ===
function Component() {
const { data, error, isError } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
retry: (failureCount, error) => {
if (error.response?.status === 404) return false;
return failureCount < 3;
},
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
onError: (error) => {
toast.error(error.message);
},
});
return <div>...</div>;
}
// === GraphQL: 结构化错误 ===
function Component() {
const { data, error } = useQuery(GET_USERS);
if (error) {
// GraphQL错误是结构化的
error.graphQLErrors.forEach((err) => {
if (err.extensions?.code === 'UNAUTHENTICATED') {
redirect('/login');
}
});
if (error.networkError) {
handleNetworkError();
}
}
return <div>...</div>;
}
// === tRPC: 类型安全错误 ===
function Component() {
const { data, error } = trpc.user.getAll.useQuery();
if (error) {
// 错误也是类型安全的
if (error.data?.code === 'UNAUTHORIZED') {
redirect('/login');
}
// 错误消息也有类型
toast.error(error.message);
}
return <div>...</div>;
}缓存策略对比
tsx
// 1. 缓存机制比较
// === SWR: Stale-While-Revalidate ===
const { data } = useSWR('/api/users', fetcher, {
// 立即返回缓存(stale),后台重新验证
revalidateOnFocus: true,
revalidateOnReconnect: true,
dedupingInterval: 2000,
});
/*
优点:
- 极快的用户体验
- 自动保持数据新鲜
- 去重优化
缺点:
- 可能显示过期数据
- 网络请求较多
*/
// === React Query: 可配置缓存 ===
const { data } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 5 * 60 * 1000, // 5分钟内不重新获取
cacheTime: 10 * 60 * 1000, // 10分钟后清除缓存
refetchOnMount: 'always',
refetchOnWindowFocus: false,
});
/*
优点:
- 高度可配置
- 精确控制缓存行为
- 性能优化
缺点:
- 配置复杂
- 需要理解多个概念
*/
// === GraphQL: 归一化缓存 ===
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
users: {
merge(existing = [], incoming) {
return [...existing, ...incoming];
},
},
},
},
},
});
const { data } = useQuery(GET_USERS);
/*
优点:
- 归一化存储,避免重复
- 自动更新相关查询
- 内存效率高
缺点:
- 配置复杂
- 学习曲线陡峭
- 调试困难
*/
// === tRPC: 基于React Query ===
const { data } = trpc.user.getAll.useQuery(undefined, {
staleTime: 5000,
cacheTime: 10000,
});
/*
优点:
- 继承React Query优点
- 类型安全
- 简单易用
缺点:
- 依赖React Query
- 功能受限于RQ
*/实时数据对比
tsx
// 1. 实时更新能力
// === SWR: 轮询 + 手动刷新 ===
const { data, mutate } = useSWR('/api/stats', fetcher, {
refreshInterval: 3000, // 3秒轮询
});
// WebSocket集成需手动处理
useEffect(() => {
const ws = new WebSocket('ws://localhost:3000');
ws.onmessage = (event) => {
mutate(JSON.parse(event.data), false);
};
return () => ws.close();
}, [mutate]);
// === React Query: 轮询 + 手动失效 ===
const { data } = useQuery({
queryKey: ['stats'],
queryFn: fetchStats,
refetchInterval: 3000,
});
// WebSocket集成
useEffect(() => {
const ws = new WebSocket('ws://localhost:3000');
ws.onmessage = () => {
queryClient.invalidateQueries({ queryKey: ['stats'] });
};
return () => ws.close();
}, []);
// === GraphQL: 原生订阅 ===
const STATS_SUBSCRIPTION = gql`
subscription OnStatsUpdated {
statsUpdated {
value
timestamp
}
}
`;
function Component() {
const { data } = useSubscription(STATS_SUBSCRIPTION);
// 自动接收实时更新
return <div>{data?.statsUpdated.value}</div>;
}
// === tRPC: WebSocket订阅 ===
function Component() {
const [stats, setStats] = useState<Stats[]>([]);
trpc.stats.onUpdate.useSubscription(undefined, {
onData: (data) => {
setStats(prev => [...prev, data]);
},
});
return <div>{stats.map(s => s.value)}</div>;
}
/*
实时能力排名:
1. GraphQL - 原生支持,最完善
2. tRPC - WebSocket集成,类型安全
3. SWR - 轮询 + 手动集成
4. React Query - 轮询 + 手动集成
5. Fetch/Axios - 完全手动
*/迁移成本对比
tsx
// 1. 从Fetch迁移到各方案的难度
// === 迁移到Axios: 容易 (1-2天) ===
// Before
const response = await fetch('/api/users');
const users = await response.json();
// After
const { data: users } = await axios.get('/api/users');
// === 迁移到SWR: 中等 (3-5天) ===
// Before
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users').then(r => r.json()).then(setUsers);
}, []);
// After
const { data: users } = useSWR('/api/users', fetcher);
// === 迁移到React Query: 中等 (5-7天) ===
// Before
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users').then(r => r.json()).then(setUsers);
}, []);
// After
const { data: users } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
// === 迁移到GraphQL: 困难 (2-4周) ===
// 需要:
// 1. 设置GraphQL服务器
// 2. 定义Schema
// 3. 编写Resolvers
// 4. 配置Apollo Client
// 5. 重写所有查询
// === 迁移到tRPC: 中等-困难 (1-3周) ===
// 需要:
// 1. TypeScript项目
// 2. 设置tRPC服务器
// 3. 定义Router
// 4. 配置客户端
// 5. 重写API层
/*
迁移成本排名(从低到高):
1. Axios - 最低,几乎直接替换
2. SWR - 较低,改变数据获取模式
3. React Query - 中等,学习概念
4. tRPC - 较高,需要全栈TypeScript
5. GraphQL - 最高,需要重新设计API
*/生态系统对比
tsx
// 1. 工具和插件
// === React Query生态 ===
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
// 插件:
// - DevTools (调试)
// - Persist (持久化)
// - Broadcast (跨标签同步)
// - Mock (测试)
// === GraphQL生态 ===
import { ApolloClient } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { onError } from '@apollo/client/link/error';
import { RetryLink } from '@apollo/client/link/retry';
import { persistCache } from 'apollo3-cache-persist';
// 工具:
// - Apollo DevTools
// - GraphQL Playground
// - GraphQL Codegen
// - Apollo Studio
// - GraphQL Voyager
// === tRPC生态 ===
import { createTRPCReact } from '@trpc/react-query';
import { httpBatchLink } from '@trpc/client';
import superjson from 'superjson';
// 集成:
// - Next.js
// - Express
// - Fastify
// - CloudFlare Workers
// === SWR生态 ===
import useSWR from 'swr';
import useSWRInfinite from 'swr/infinite';
import useSWRMutation from 'swr/mutation';
// 较小,主要是核心库
/*
生态系统排名:
1. GraphQL - 最丰富,工具最多
2. React Query - 完善,插件多
3. Axios - 成熟,社区大
4. tRPC - 新兴,快速增长
5. SWR - 较小,专注核心
*/实际项目案例
案例1: 电商平台
tsx
/*
项目: 大型电商平台
需求:
- 复杂的数据关系(商品、订单、用户)
- 高性能要求
- 移动端优化
- 实时库存更新
选择: GraphQL + Apollo Client
理由:
✅ 精确获取数据,减少传输
✅ 归一化缓存,避免重复
✅ 实时订阅支持
✅ 适合复杂数据关系
结果:
- API请求减少60%
- 数据传输减少40%
- 首屏加载提升30%
*/
// 实现示例
const PRODUCT_QUERY = gql`
query GetProduct($id: ID!) {
product(id: $id) {
id
name
price
inventory
reviews(first: 5) {
rating
comment
}
relatedProducts {
id
name
price
}
}
}
`;
function ProductPage({ id }) {
const { data } = useQuery(PRODUCT_QUERY, {
variables: { id },
});
const [subscribe] = useSubscription(INVENTORY_SUBSCRIPTION, {
variables: { productId: id },
onData: ({ data }) => {
// 实时更新库存
cache.modify({
id: cache.identify({ __typename: 'Product', id }),
fields: {
inventory: () => data.inventoryUpdated.quantity,
},
});
},
});
return <ProductDetail product={data?.product} />;
}案例2: 内部管理系统
tsx
/*
项目: 企业内部管理系统
需求:
- 快速开发
- 类型安全
- 简单维护
- 团队都用TypeScript
选择: tRPC + React Query
理由:
✅ 端到端类型安全
✅ 无需Schema定义
✅ 开发效率高
✅ 重构友好
结果:
- 开发速度提升50%
- 运行时错误减少80%
- 代码量减少40%
*/
// 实现示例
// 服务端
const appRouter = router({
user: router({
list: publicProcedure
.input(z.object({
page: z.number(),
search: z.string().optional(),
}))
.query(async ({ input }) => {
return db.user.findMany({
where: input.search ? {
name: { contains: input.search },
} : undefined,
skip: input.page * 20,
take: 20,
});
}),
create: protectedProcedure
.input(z.object({
name: z.string(),
email: z.string().email(),
}))
.mutation(async ({ input }) => {
return db.user.create({ data: input });
}),
}),
});
// 客户端
function UserManagement() {
const [page, setPage] = useState(0);
const [search, setSearch] = useState('');
const { data: users } = trpc.user.list.useQuery({ page, search });
const { mutate: createUser } = trpc.user.create.useMutation({
onSuccess: () => {
utils.user.list.invalidate();
},
});
// 完全类型安全,自动补全
return <div>...</div>;
}案例3: 新闻资讯应用
tsx
/*
项目: 新闻资讯移动应用
需求:
- 实时数据更新
- 离线阅读
- 包体积小
- 简单易用
选择: SWR
理由:
✅ 轻量级(~5KB)
✅ 自动重新验证
✅ 开箱即用
✅ 学习成本低
结果:
- 包体积最小化
- 用户体验流畅
- 开发周期短
*/
// 实现示例
function NewsList() {
const { data: news } = useSWR('/api/news', fetcher, {
refreshInterval: 30000, // 30秒刷新
revalidateOnFocus: true,
revalidateOnReconnect: true,
});
const { data: trending } = useSWR('/api/trending', fetcher, {
refreshInterval: 60000,
});
return (
<div>
<TrendingSection data={trending} />
<NewsSection data={news} />
</div>
);
}
// 离线支持
const { data } = useSWR('/api/news', fetcher, {
fallbackData: cachedNews, // 离线时使用缓存
onSuccess: (data) => {
localStorage.setItem('news', JSON.stringify(data));
},
});案例4: 数据分析仪表板
tsx
/*
项目: 实时数据分析仪表板
需求:
- 多数据源
- 复杂缓存策略
- 分页和无限滚动
- 性能监控
选择: TanStack Query
理由:
✅ 功能最完整
✅ 强大的缓存管理
✅ 优秀的DevTools
✅ 灵活配置
结果:
- 缓存命中率90%
- 请求优化70%
- 用户体验提升
*/
// 实现示例
function Dashboard() {
// 并行查询多个数据源
const queries = useQueries({
queries: [
{ queryKey: ['sales'], queryFn: fetchSales, staleTime: 60000 },
{ queryKey: ['users'], queryFn: fetchUsers, staleTime: 30000 },
{ queryKey: ['orders'], queryFn: fetchOrders, staleTime: 10000 },
],
});
// 无限滚动
const {
data,
fetchNextPage,
hasNextPage,
} = useInfiniteQuery({
queryKey: ['transactions'],
queryFn: ({ pageParam = 0 }) => fetchTransactions(pageParam),
getNextPageParam: (lastPage) => lastPage.nextCursor,
});
// 依赖查询
const userId = queries[1].data?.currentUserId;
const { data: userDetails } = useQuery({
queryKey: ['userDetails', userId],
queryFn: () => fetchUserDetails(userId),
enabled: !!userId,
});
return <div>...</div>;
}最终建议矩阵
场景 推荐方案 备选方案
─────────────────────────────────────────────────
小型项目(<5页面) Fetch + Hook SWR
中型项目(5-20页面) SWR React Query
大型项目(>20页面) React Query tRPC/GraphQL
企业应用 tRPC React Query
电商平台 GraphQL React Query
移动应用 GraphQL/SWR React Query
实时应用 GraphQL tRPC
内部工具 tRPC React Query
新项目 tRPC React Query
遗留项目迁移 Axios SWR
团队技能 推荐方案 备选方案
─────────────────────────────────────────────────
JavaScript only SWR Axios
TypeScript tRPC React Query
GraphQL熟悉 GraphQL -
新手团队 SWR Fetch
经验团队 tRPC/GraphQL React Query
性能要求 推荐方案 备选方案
─────────────────────────────────────────────────
极致性能 SWR React Query
包体积最小 tRPC Fetch
首屏速度 SWR Fetch
运行时性能 React Query SWR
内存占用最小 Fetch tRPC总结与展望
当前状态(2024)
- React Query/TanStack Query: 最受欢迎,功能最全
- tRPC: 快速增长,TypeScript项目首选
- GraphQL: 成熟稳定,大型项目标配
- SWR: 轻量简洁,小项目利器
- Fetch/Axios: 基础工具,永不过时
未来趋势
- 类型安全: 端到端类型安全成为标准
- 性能优化: 自动化性能优化和监控
- 离线优先: 更好的离线支持和同步
- AI集成: 智能预取和缓存策略
- 边缘计算: 边缘渲染和数据获取
选择清单
在选择数据获取方案时,考虑以下因素:
✅ 项目规模: 小/中/大 ✅ 团队技能: JS/TS/GraphQL ✅ 性能要求: 首屏/运行时/内存 ✅ 类型安全: 弱/强/极强 ✅ 开发效率: 快速/稳健/长期 ✅ 维护成本: 学习曲线/文档/社区 ✅ 迁移风险: 锁定/灵活/可替换
记住: 没有银弹,只有权衡。选择最适合你项目的方案,而不是最流行的方案。