当前位置: 首页 > news >正文

GraphQL 客户端:别再写冗长的 REST 请求了

GraphQL 客户端:别再写冗长的 REST 请求了

毒舌时刻

这代码写得跟网红滤镜似的——仅供参考。

各位前端同行,咱们今天聊聊 GraphQL 客户端。别告诉我你还在写冗长的 REST 请求,那感觉就像用写信的方式发微信——能发,但太慢了。

为什么你需要 GraphQL

最近看到一个项目,一个页面要调 5 个 REST 接口,还要手动合并数据。我就想问:你是在写前端还是在写数据合并工具?

反面教材

// 反面教材:冗长的 REST 请求 // services/api.ts async function getDashboardData(userId: string) { // 请求用户信息 const userRes = await fetch(`/api/users/${userId}`); const user = await userRes.json(); // 请求用户订单 const ordersRes = await fetch(`/api/users/${userId}/orders`); const orders = await ordersRes.json(); // 请求订单详情(N+1 问题) const ordersWithDetails = await Promise.all( orders.map(async (order: any) => { const detailsRes = await fetch(`/api/orders/${order.id}/details`); const details = await detailsRes.json(); return { ...order, details }; }) ); // 请求推荐商品 const recommendationsRes = await fetch(`/api/users/${userId}/recommendations`); const recommendations = await recommendationsRes.json(); // 手动合并数据 return { user, orders: ordersWithDetails, recommendations }; } // 使用 const data = await getDashboardData('123'); // 发了 3 + N 个请求,还要手动合并 😭

毒舌点评:这代码,一个页面发 8 个请求,你是在写前端还是在写 DDoS 攻击工具?

GraphQL 的正确姿势

1. 基础查询

// 正确姿势:一个请求搞定所有 // queries/dashboard.ts import { gql } from '@apollo/client'; const GET_DASHBOARD = gql` query GetDashboard($userId: ID!) { user(id: $userId) { id name email avatar orders { id total status createdAt items { id name price quantity } } recommendations { id name price image } } } `; // 使用 import { useQuery } from '@apollo/client'; function Dashboard() { const { data, loading, error } = useQuery(GET_DASHBOARD, { variables: { userId: '123' } }); if (loading) return <div>加载中...</div>; if (error) return <div>错误: {error.message}</div>; const { user } = data; return ( <div> <h1>欢迎, {user.name}</h1> <OrderList orders={user.orders} /> <Recommendations items={user.recommendations} /> </div> ); }

2. 自动缓存

// 正确姿势:自动缓存 import { ApolloClient, InMemoryCache } from '@apollo/client'; const client = new ApolloClient({ uri: 'https://api.example.com/graphql', cache: new InMemoryCache({ typePolicies: { Query: { fields: { user: { merge(existing, incoming) { return incoming; } } } } } }) }); // 第一次请求 - 从服务器获取 const { data } = useQuery(GET_USER, { variables: { id: '1' } }); // 第二次请求 - 从缓存读取,瞬间完成! const { data: cachedData } = useQuery(GET_USER, { variables: { id: '1' } });

3. 实时订阅

// 正确姿势:实时数据 // subscriptions/notifications.ts import { gql } from '@apollo/client'; const NOTIFICATIONS_SUBSCRIPTION = gql` subscription OnNotification { notification { id type message createdAt } } `; // 使用 import { useSubscription } from '@apollo/client'; function NotificationBell() { const { data } = useSubscription(NOTIFICATIONS_SUBSCRIPTION); useEffect(() => { if (data) { toast.info(data.notification.message); } }, [data]); return <BellIcon hasUnread={!!data} />; }

4. 乐观更新

// 正确姿势:乐观更新 // mutations/addToCart.ts import { gql } from '@apollo/client'; const ADD_TO_CART = gql` mutation AddToCart($productId: ID!, $quantity: Int!) { addToCart(productId: $productId, quantity: $quantity) { id items { id quantity product { name price } } total } } `; // 使用 import { useMutation } from '@apollo/client'; function ProductCard({ product }) { const [addToCart] = useMutation(ADD_TO_CART, { optimisticResponse: { addToCart: { id: 'temp-id', items: [ { id: 'temp-item', quantity: 1, product: { name: product.name, price: product.price, __typename: 'Product' }, __typename: 'CartItem' } ], total: product.price, __typename: 'Cart' } }, update(cache, { data: { addToCart } }) { cache.writeQuery({ query: GET_CART, data: { cart: addToCart } }); } }); return ( <button onClick={() => addToCart({ variables: { productId: product.id, quantity: 1 } })}> 加入购物车 </button> ); }

毒舌点评:这才叫现代数据获取,精确获取、自动缓存、实时更新。别告诉我你还在写 REST。

实战技巧:GraphQL 最佳实践

1. 代码生成

// codegen.ts import type { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { schema: 'https://api.example.com/graphql', documents: ['src/**/*.tsx'], generates: { './src/gql/': { preset: 'client', plugins: [] } } }; export default config;
# 生成 TypeScript 类型 $ graphql-codegen # 生成的代码 // src/gql/graphql.ts export type GetUserQuery = { user: { id: string; name: string; email: string; } };

2. 错误处理

// 正确姿势:错误处理 import { onError } from '@apollo/client/link/error'; const errorLink = onError(({ graphQLErrors, networkError }) => { if (graphQLErrors) { graphQLErrors.forEach(({ message, locations, path }) => { console.error( `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}` ); }); } if (networkError) { console.error(`[Network error]: ${networkError}`); } }); const client = new ApolloClient({ link: from([errorLink, httpLink]), cache: new InMemoryCache() });

3. 分页查询

// 正确姿势:分页 const GET_USERS = gql` query GetUsers($first: Int!, $after: String) { users(first: $first, after: $after) { edges { node { id name email } cursor } pageInfo { hasNextPage endCursor } } } `; function UserList() { const { data, fetchMore } = useQuery(GET_USERS, { variables: { first: 10 } }); const loadMore = () => { fetchMore({ variables: { after: data.users.pageInfo.endCursor } }); }; return ( <div> {data.users.edges.map(({ node }) => ( <UserCard key={node.id} user={node} /> ))} {data.users.pageInfo.hasNextPage && ( <button onClick={loadMore}>加载更多</button> )} </div> ); }

4. 文件上传

// 正确姿势:文件上传 const UPLOAD_FILE = gql` mutation UploadFile($file: Upload!) { uploadFile(file: $file) { id url filename } } `; function FileUpload() { const [uploadFile] = useMutation(UPLOAD_FILE); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; if (file) { uploadFile({ variables: { file } }); } }; return <input type="file" onChange={handleChange} />; }

5. 常见陷阱

// 陷阱1:忘记使用 fragments // ❌ 重复定义字段 const QUERY1 = gql` query { user { id name email avatar } } `; const QUERY2 = gql` query { user { id name email avatar phone } } `; // ✅ 使用 fragment const USER_FIELDS = gql` fragment UserFields on User { id name email avatar } `; const QUERY1 = gql` ${USER_FIELDS} query { user { ...UserFields } } `; // 陷阱2:N+1 查询(虽然 GraphQL 也有这个问题) // 确保后端使用 DataLoader // 陷阱3:查询过大 // ❌ 查询所有字段 const BAD_QUERY = gql` query { users { id name email phone address orders { id items { id product { id name description price category { id name } } } } } } `; // ✅ 只查询需要的字段 const GOOD_QUERY = gql` query { users { id name email } } `;

最后想说的

GraphQL 不是可选的,是现代应用数据层的最佳选择。别再写冗长的 REST 请求了——用上 GraphQL,你的数据获取会简洁 80%。

记住:好的数据层应该是灵活的,前端决定要什么数据,后端只提供能力。GraphQL 让这一切变得简单。

http://www.jsqmd.com/news/553667/

相关文章:

  • ClawdBot部署全攻略:手把手教你配置个人AI助手
  • 从陀螺玩具到卫星控制:反作用飞轮的物理原理跨界解析
  • SiameseAOE中文-base效果展示:支持中英文混合评论(如‘very good#满意’)抽取
  • Playwright 测试:别再手动点点点了
  • TranslucentTB启动故障攻克指南:从注册表修复到组件优化的完整方案
  • DLSS动态适配引擎:三步构建游戏图形性能优化系统
  • 如何用AMD Ryzen调试神器彻底掌控你的硬件性能
  • 告别卡顿:优化Qt+高德地图混合开发性能的5个实战技巧(QWebEngineView调优)
  • C++的std--integral_constant编译期整数常量在模板元编程中的基础
  • 别只盯着AgentScope了!这5个多智能体框架,帮你搞定不同场景的AI应用
  • Qwen-Image-2512在Linux系统下的高效部署方案
  • STM32G431无感FOC驱动实战:手把手教你配置HFI+SMO,实现电机零速带载启动
  • 5分钟零基础入门:BepInEx Unity游戏插件框架快速上手教程
  • UE5 Niagara新手教程:用条带渲染器为角色制作酷炫移动拖尾特效(附蓝图设置)
  • 3分钟快速掌握:Onekey Steam Depot清单下载器终极指南
  • Dify工作流编排技术:解决企业级AI应用开发中的流程标准化难题
  • payload-dumper-go:智能汽车系统OTA包高效提取工具,释放嵌入式镜像价值
  • Qwen3.5-35B-A3B-AWQ-4bit效果惊艳集锦:设计师作品图智能描述+风格标签生成
  • 如何通过FunClip构建本地AI视频剪辑工作流:从语音识别到智能剪辑
  • 自动化测试卡证检测模型:Python脚本构建评测数据集
  • Autoresearch 深度解析
  • YOLO12模型与Python入门教程:从零开始学AI目标检测
  • 如何高效恢复Windows Defender:专业级系统安全重建指南
  • 图片旋转判断模型在文档处理中的创新应用
  • Steam成就管理终极指南:如何轻松掌控你的游戏成就
  • translategemma-4b-it镜像免配置:自动检测CUDA版本并匹配最优kernel
  • Llama-3.2V-11B-cot与计算机网络知识结合:自动生成网络配置脚本与排错指南
  • 【Python内存泄漏终结者】:20年资深工程师亲授5大精准定位与修复技巧
  • EICopilot:引爆知识图谱搜索革命!大模型驱动下,企业信息检索效率提升82%!
  • 如何配置LyricsX桌面歌词插件:完整实战指南