GraphQL实战:从入门到项目落地的完整指南

小雯婷 框架 阅读 855
赞 28 收藏
二维码
手机扫码查看
反馈

先上手再补课:我怎么在项目里用 GraphQL 的

说实话,第一次接触 GraphQL 的时候,我脑子里全是 RESTful API 的影子。什么 GET /users、POST /orders,一套流程跑得飞起。但后来公司新项目要对接一个数据源特别杂的后端,字段动不动就变,前端每次都要跟着改接口——烦死了。这时候同事甩给我一句:“试试 GraphQL 吧,你想要啥字段自己写 query 就行。”

GraphQL实战:从入门到项目落地的完整指南

我一开始不信,直到自己动手写了第一个请求,才发现真香。下面直接上代码,别管啥理论,先跑起来再说。

// 用 fetch 发一个最简单的 GraphQL 请求
const query = 
  query GetUser($id: ID!) {
    user(id: $id) {
      name
      email
      avatarUrl
    }
  }
;

fetch('https://jztheme.com/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query,
    variables: { id: '123' }
  })
})
.then(res => res.json())
.then(data => console.log(data));

看到没?前端自己决定要哪些字段,后端不用为每个页面单独开接口。这个模式在后台管理系统或者数据看板类项目里简直救命——产品经理今天要加个“注册渠道”,明天要删个“最后登录时间”,你再也不用求后端大哥改接口了。

这个场景最好用:嵌套数据 + 条件查询

GraphQL 最爽的地方其实是处理嵌套数据。比如我要查一个订单,连带查用户信息、商品列表、物流状态,RESTful 得调三四个接口,而 GraphQL 一行 query 搞定:

query GetOrderDetail($orderId: ID!) {
  order(id: $orderId) {
    id
    createdAt
    status
    user {
      name
      phone
    }
    items {
      product {
        title
        price
        thumbnail
      }
      quantity
    }
    shipment {
      trackingNumber
      status
      estimatedDelivery
    }
  }
}

亲测有效,而且后端只要实现好 resolver,前端完全不用关心数据是从几个数据库表拼出来的。不过这里有个坑:**别贪心一次查太多层**。我之前写了个五层嵌套的 query,结果后端 resolver 没做缓存,N+1 查询直接把数据库干崩了(别问我是谁)。

踩坑提醒:这三点一定注意

GraphQL 虽好,但新手容易栽跟头。这几个坑我全踩过,现在给你标红:

  • 变量类型必须严格匹配:比如你在 query 里声明 $userId: ID!,传 { userId: 123 } 是不行的!ID 类型在 GraphQL 里是字符串,得传 '123'。否则会报 “Variable “$userId” of type “ID!” used in position expecting type “ID!”” 这种看起来很矛盾的错(其实是因为你传了 number)。
  • 错误处理和 REST 不一样:即使接口出错了,HTTP 状态码还是 200!错误信息藏在返回体的 errors 字段里。所以你不能只判断 res.ok,得额外检查 data.errors
fetch('/graphql', { /* ... */ })
  .then(res => res.json())
  .then(result => {
    if (result.errors) {
      console.error('GraphQL errors:', result.errors);
      // 这里处理错误
    } else {
      // 正常走 data
    }
  });
  • 别在 query 里写业务逻辑:见过有人把分页参数写成 $page: Int = 1, $pageSize: Int = 10,然后前端随便传个 99999 想拉全量数据……后端没做限制的话,直接 OOM。建议后端对敏感参数做强校验,前端也别滥用默认值。

进阶技巧:用代码生成器省掉手写 query

手写 query 和 TypeScript 类型映射太痛苦了。我折腾半天发现,用 graphql-codegen 自动生成 types 和 hooks,效率翻倍。

配置文件 codegen.yml 长这样:

overwrite: true
schema: "https://jztheme.com/graphql"
documents: "src/**/*.graphql"
generates:
  src/gql/:
    preset: client

然后你在 src/pages/Home.query.graphql 里写:

query GetPosts {
  posts(limit: 10) {
    id
    title
    author {
      name
    }
  }
}

运行 graphql-codegen,它会自动生成:

  • TypeScript 接口(比如 GetPostsQuery
  • React 的 useGetPostsQuery hook(如果你用 Apollo Client)

亲测有效,从此告别手写 interface 和拼错字段名。唯一要注意的是:schema 变更后记得重新生成,不然类型对不上。

缓存策略:Apollo Client 别乱配

很多人用 Apollo Client 默认配置,结果列表页切换详情再回来,数据没了。这是因为 Apollo 默认按 __typename + id 做 normalize 缓存。如果你的 entity 没有全局唯一的 id 字段,它就当成新对象处理。

解决方案有两个:

  • 让后端所有类型都返回 id(最推荐)
  • 自定义 typePolicies,比如用 path 或组合字段当 key:
const cache = new InMemoryCache({
  typePolicies: {
    Post: {
      keyFields: ['slug'] // 用 slug 当唯一标识
    },
    Comment: {
      keyFields: ['postId', 'index'] // 组合字段
    }
  }
});

我之前在一个内容平台项目里,文章用 slug 做路由,结果列表和详情页的数据对不上,折腾半天才发现是缓存 key 没配对。血泪教训。

最后说两句

GraphQL 不是银弹。如果你的 API 很简单,就几个固定接口,那 REST 完全够用。但一旦涉及复杂数据组合、频繁变更字段、多端(Web/iOS/Android)共用后端,GraphQL 的优势就出来了。

另外,别迷信“前端完全自治”。后端 schema 设计不合理,resolver 写得烂,照样卡成狗。前后端得一起对齐设计,比如约定好分页格式、错误码规范、字段命名风格。

以上是我踩坑后的总结,希望对你有帮助。这个技术的拓展用法还有很多(比如订阅、文件上传、 persisted queries),后续会继续分享这类博客。有更优的实现方式欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论