GraphQL实战:从入门到项目优化的完整指南

Prog.小青 框架 阅读 2,676
赞 21 收藏
二维码
手机扫码查看
反馈

先上手再说,GraphQL 真没那么玄

我第一次接触 GraphQL 时,还以为又是个包装 REST 的花架子。结果项目一上手,发现它真能省不少事——尤其是前端要的数据结构变来变去的时候。别被那些“声明式查询”“类型系统”吓到,咱先跑起来再说。

GraphQL实战:从入门到项目优化的完整指南

最简单的用法:直接用 fetch 发个 POST 请求。别急着装 Apollo,小项目根本用不着。亲测有效,代码就几行:

const query = 
  query GetUser($id: ID!) {
    user(id: $id) {
      name
      email
      posts {
        title
        publishedAt
      }
    }
  }
;

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));

看到没?你只要告诉后端你要啥字段,它就只给你这些。不像 REST,一个接口返回一堆你根本不用的字段,还得自己筛。这在移动端或者弱网环境下特别香。

这个场景最好用:动态字段需求

我们之前有个后台管理页,产品经理隔两天就改一次列表要显示的字段。用 REST 的话,要么后端加新接口,要么前端拿全量数据自己切。后来换成 GraphQL,前端自己改 query 就行,后端动都不用动。

比如今天要用户头像,明天要用户等级,后天又要关联的订单数——只要 schema 里有,前端直接加字段就行。下面这个例子,我注释掉一行就能切换是否加载评论:

const query = 
  query GetPost($postId: ID!) {
    post(id: $postId) {
      title
      content
      author { name }
      // comments { body, createdAt }  // 不需要就注释掉
    }
  }
;

这种灵活性在复杂表单、仪表盘类页面特别实用。不过注意,别滥用嵌套,后面会讲坑。

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

GraphQL 虽好,但有几个坑我踩过不止一次,分享出来帮你避雷:

  • 嵌套太深容易 N+1 查询:后端如果没做好 DataLoader 优化,你查一个用户带 10 个帖子,每个帖子又带作者,可能触发 1 + 10 + 10 次数据库查询。我见过接口慢到 5 秒,就是因为这个。建议和后端约定最大嵌套层级,或者用批处理工具。
  • 错误处理比 REST 麻烦:REST 直接看 HTTP 状态码,200 就成功,401 就登录。但 GraphQL 无论成功失败都返回 200,错误信息藏在 data.errors 里。前端必须每次都检查 response.data.errors,不然用户登录过期了你都不知道。我写了个通用拦截器:
function handleGraphQLError(response) {
  if (response.data?.errors) {
    const err = response.data.errors[0];
    if (err.extensions?.code === 'UNAUTHENTICATED') {
      // 跳转登录
      window.location.href = '/login';
    }
    throw new Error(err.message);
  }
  return response.data;
}
  • 缓存不好搞:REST 的 URL 天然适合做缓存 key,但 GraphQL 的 query 是动态字符串,稍微变量不同就变成新请求。Apollo 有 normalized cache,但配置复杂。小项目我干脆放弃缓存,或者用 JSON.stringify(query + variables) 当 key 手动存 localStorage,虽然糙但有效。

进阶技巧:用 fragments 减少重复代码

当你多个地方要用同样的字段结构,比如用户卡片在首页、详情页、评论区都出现,别复制粘贴 query。用 fragment 抽离:

const USER_FRAGMENT = 
  fragment UserFields on User {
    id
    name
    avatar
    isVerified
  }
;

const query1 = 
  ${USER_FRAGMENT}
  query GetPost($id: ID!) {
    post(id: $id) {
      author { ...UserFields }
    }
  }
;

const query2 = 
  ${USER_FRAGMENT}
  query GetComments($postId: ID!) {
    comments(postId: $postId) {
      author { ...UserFields }
    }
  }
;

这样改字段只需要改一处。我团队现在强制要求超过两次复用的结构必须用 fragment,不然 PR 直接打回。

要不要上 Apollo?看情况

很多人一上来就装 Apollo Client,其实不一定需要。如果你只是偶尔发几个 query,用原生 fetch + 自定义 hook 就够了。我封装了一个轻量级的:

function useGraphQL(query, variables) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch('https://jztheme.com/graphql', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ query, variables })
    })
    .then(res => res.json())
    .then(res => {
      if (res.errors) {
        setError(res.errors[0].message);
      } else {
        setData(res.data);
      }
    })
    .catch(setError)
    .finally(() => setLoading(false));
  }, [query, JSON.stringify(variables)]);

  return { data, loading, error };
}

不到 30 行,搞定基本需求。只有当你需要本地状态管理、复杂缓存、离线支持时,才考虑 Apollo。别为了用框架而用框架,增加 bundle 体积不值得。

最后说两句

GraphQL 不是银弹,但它确实解决了前端数据获取的很多痛点。特别是当你的产品迭代快、接口变动频繁时,它能让你少求后端几次。不过记住,再好的工具也得配合良好的后端实现,否则性能反而更差。

以上是我踩坑后的总结,希望对你有帮助。这个技术的拓展用法还有很多,比如用 GraphQL Subscriptions 做实时更新,或者用 persisted queries 优化性能,后续会继续分享这类博客。有更优的实现方式欢迎评论区交流,比如你们怎么处理缓存的?

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

暂无评论