graphql-request vs urql vs apollo-client vs react-apollo
GraphQL 客户端库选型与架构对比
graphql-requesturqlapollo-clientreact-apollo類似套件:

GraphQL 客户端库选型与架构对比

apollo-client(现主要为 @apollo/client)、graphql-request、react-apollo 和 urql 都是用于在 JavaScript 应用中与 GraphQL API 交互的工具。apollo-client 是功能最全面的企业级解决方案,内置强大的缓存和管理功能。graphql-request 是一个极简的轻量级客户端,适合不需要复杂缓存的场景。urql 主打模块化和高性能,提供类似 Apollo 的体验但更轻量。react-apollo 是 Apollo 早期的 React 集成包,现已废弃并合并至 @apollo/client。

npm下載趨勢

3 年

GitHub Stars 排名

統計詳情

套件
下載數
Stars
大小
Issues
發布時間
許可
graphql-request13,112,8486,121420 kB601 年前MIT
urql1,518,1508,985140 kB332 個月前MIT
apollo-client583,95619,781-4106 年前MIT
react-apollo167,0586,790-2057 年前MIT

GraphQL 客户端库:Apollo, urql, graphql-request 与 react-apollo 深度对比

在前端开发中,选择正确的 GraphQL 客户端直接影响应用的性能、可维护性和开发体验。apollo-client、graphql-request、urql 以及已废弃的 react-apollo 代表了不同的设计哲学。本文将深入对比它们的核心架构、React 集成方式以及适用场景,帮助你做出明智的技术选型。

⚠️ 重要维护状态警告

在深入技术细节前,必须明确两个包的生命周期状态,这直接决定了它们能否用于生产环境。

react-apollo 已废弃 react-apollo 是 Apollo Client 2.x 时代的 React 绑定包。从 Apollo Client 3.0 开始,React 集成已合并到主包 @apollo/client 中。官方明确标记 react-apollo 为 deprecated。

// ❌ 错误:不要在新项目中使用
import { useQuery } from 'react-apollo';

// ✅ 正确:使用统一的客户端包
import { useQuery } from '@apollo/client';

apollo-client 包名变更 未作用域的 apollo-client 包也是旧版本。现代开发应直接使用 @apollo/client,它包含了核心客户端和 React Hooks。

// ❌ 旧版包
import ApolloClient from 'apollo-client';

// ✅ 现代包
import { ApolloClient, InMemoryCache } from '@apollo/client';

🗄️ 核心架构:缓存与状态管理

客户端如何处理数据缓存是区分它们的关键。全功能客户端会管理全局缓存,而轻量级客户端仅负责发送请求。

apollo-client (@apollo/client) 拥有最强大的标准化缓存(InMemoryCache)。它自动将数据按 ID 归一化,更新一处数据会自动反映到所有使用该数据的组件。

// @apollo/client: 自动缓存归一化
const client = new ApolloClient({
  uri: '/graphql',
  cache: new InMemoryCache()
});

// 更新用户名字,所有引用该用户的地方都会自动更新
client.writeFragment({
  id: 'User:1',
  fragment: gql`fragment UserName on User { name }`,
  data: { name: 'New Name' }
});

urql 提供模块化缓存系统。默认有简单缓存,但支持交换(exchanges)机制来自定义缓存行为,灵活性介于 Apollo 和轻量级客户端之间。

// urql: 可配置的缓存交换
import { createClient, defaultExchanges } from 'urql';

const client = createClient({
  url: '/graphql',
  exchanges: [...defaultExchanges] // 可插入自定义缓存逻辑
});

graphql-request 无内置缓存。每次请求都会发送网络请求。开发者需自行决定如何使用 React Query 或 SWR 等工具配合缓存。

// graphql-request: 无缓存,每次都是新请求
import { request } from 'graphql-request';

// 每次调用都会发起网络请求
const data = await request(endpoint, query);

react-apollo (遗留)架构同旧版 Apollo Client。缓存机制与当前 @apollo/client 类似,但不再接收更新。

// react-apollo: 遗留架构
// 内部使用 ApolloClient 缓存,但包已停止维护
// 不建议展示具体代码,应迁移至 @apollo/client

🎣 React 集成:Hooks 体验

对于 React 开发者,Hooks 的易用性和功能深度至关重要。

apollo-client (@apollo/client) 提供最全的 Hooks 家族(useQuery, useMutation, useSubscription, useLazyQuery)。支持复杂的配置如 polling, skip, refetch。

// @apollo/client: 功能丰富的 Hooks
import { useQuery } from '@apollo/client';

function UserProfile() {
  const { data, loading } = useQuery(GET_USER, {
    variables: { id: 1 },
    pollInterval: 5000 // 支持轮询
  });
  return <div>{data?.user.name}</div>;
}

urql Hooks API 设计简洁,返回数组 [data, error] 或对象。强调自定义 Hooks 的封装,代码通常更短。

// urql: 简洁的 Hooks
import { useQuery } from 'urql';

function UserProfile() {
  const [result] = useQuery({
    query: GET_USER,
    variables: { id: 1 }
  });
  return <div>{result.data?.user.name}</div>;
}

graphql-request 不提供 React Hooks。它是一个通用函数,需在 useEffect 中手动调用,或配合 React Query 使用。

// graphql-request: 需手动集成 React
import { request } from 'graphql-request';
import { useEffect, useState } from 'react';

function UserProfile() {
  const [user, setUser] = useState(null);
  useEffect(() => {
    request(endpoint, GET_USER).then(setUser);
  }, []);
  return <div>{user?.name}</div>;
}

react-apollo (遗留)提供了早期的 Hooks 实现(useQuery, useMutation),但 API 细节与当前 @apollo/client 有细微差别,且不再维护。

// react-apollo: 遗留 Hooks
// import { useQuery } from 'react-apollo'; 
// API 类似但包已废弃,强烈不建议使用

📦 复杂度与体积权衡

选择库时,需考虑引入的复杂度是否值得。

特性@apollo/client (Apollo)urqlgraphql-requestreact-apollo
缓存管理🟢 自动归一化,强大🟡 可配置,模块化🔴 无,需自行处理🟢 自动 (已废弃)
React Hooks🟢 内置,功能全🟢 内置,简洁🔴 无,需手写🟢 内置 (已废弃)
包体积🔴 较大🟢 较小🟢 极小🔴 较大
学习曲线🔴 陡峭🟡 中等🟢 平缓🔴 陡峭
维护状态🟢 活跃🟢 活跃🟢 活跃🔴 废弃

💡 真实场景选型建议

场景 1:企业级管理后台

你需要复杂的缓存更新、乐观 UI 和权限控制。

  • ✅ 最佳选择: @apollo/client
  • 理由:内置功能最丰富,减少重复代码,生态完善。

场景 2:简单营销页面或 Node.js 脚本

只需要获取一次数据,不需要缓存状态。

  • ✅ 最佳选择: graphql-request
  • 理由:依赖极少,代码直观,无多余负担。

场景 3:追求性能的中大型 React 应用

想要 Apollo 的体验,但希望更轻量且可定制。

  • ✅ 最佳选择: urql
  • 理由:平衡了功能与体积,缓存机制灵活。

场景 4:旧项目迁移

项目正在使用 react-apollo 或 apollo-client (未作用域)。

  • ✅ 最佳选择: 迁移到 @apollo/client
  • 理由:获得安全更新和新特性,API 迁移成本相对较低。

📌 总结

@apollo/client 是功能最全的重型武器 🛡️,适合复杂应用,但需注意使用正确的包名。 urql 是灵活的轻骑兵 🏇,适合追求性能和定制化的 React 团队。 graphql-request 是精简的工具刀 🔪,适合简单请求或非 React 环境。 react-apollo 是历史遗留物 🕸️,请勿在新项目中使用。

最终建议:除非你有特殊理由,否则新 React 项目应在 @apollo/client 和 urql 之间选择。若需极简方案,选 graphql-request 并搭配 React Query 管理状态。

如何選擇: graphql-request vs urql vs apollo-client vs react-apollo

  • graphql-request:

    选择 graphql-request 如果你只需要简单的 GraphQL 请求,不需要客户端缓存或状态管理。适合 Node.js 脚本、服务端代码或对 bundle 大小极其敏感的简单前端页面。

  • urql:

    选择 urql 如果你想要类似 Apollo 的 React 体验,但希望包体积更小、配置更灵活。适合中大型 React 应用,特别是需要自定义缓存策略或追求更高性能的场景。

  • apollo-client:

    选择 apollo-client(实际应使用 @apollo/client)如果你需要企业级功能,如复杂的缓存归一化、乐观更新、离线支持和强大的开发者工具。适合大型复杂应用,但需注意包名已更新,旧版 apollo-client 不再维护。

  • react-apollo:

    不要在新项目中使用 react-apollo。该包已官方废弃,功能已完全合并到 @apollo/client 中。继续使用旧包会导致缺乏安全更新和新特性支持,请立即迁移到 @apollo/client。

graphql-request 的 README

graphql-request

Minimal GraphQL client supporting Node and browsers for scripts or simple apps.

GitHub Action npm version

Highlights

  • Most simple & lightweight GraphQL client
  • Promise-based API (works with async / await)
  • Pure ESM package
  • First class TypeScript support
    • Including TypedDocumentNode
  • Isomorphic (works in both Node and Browsers)

Install

npm add graphql-request graphql

TypeScript Setup

This package uses package.exports. Therefore if you are a TypeScript user you must:

  1. have your tsconfig.json moduleResolution set to "bundler" or "node16"/"nodenext".
  2. Have your package.json type set to "module".

Quick Start

Send a GraphQL document using a static request function:

import { gql, request } from 'graphql-request'

const document = gql`
  {
    company {
      ceo
    }
  }
`
await request('https://api.spacex.land/graphql/', document)

The function can be passed a configuration object for more complex cases:

await request({
  url,
  document,
  variables,
  requestHeaders,
})

A class is available for constructing your own instances:

import { gql, GraphQLClient } from 'graphql-request'

const document = gql`
  {
    company {
      ceo
    }
  }
`
const endpoint = 'https://api.spacex.land/graphql/'
const client = new GraphQLClient(endpoint)
await client.request(document)

Examples

Node Version Support

We only (officially) support versions of Nodejs of the following status:

  • Current
  • LTS
  • Maintenance and end of life not yet reached

So for example on Oct 24 2023 that would mean these versions: 18, 20, 21.

Any issue that exists solely for an unsupported version of Nodejs will be rejected (not worked on).

Reference

⚠️ This reference is incomplete. Check out the examples for more reference material.

Configuration

ErrorPolicy

By default GraphQLClient will throw when an error is received. However, sometimes you still want to resolve the (partial) data you received. You can define errorPolicy in the GraphQLClient constructor.

const client = new GraphQLClient(endpoint, { errorPolicy: 'all' })
None (default)

Allow no errors at all. If you receive a GraphQL error the client will throw.

Ignore

Ignore incoming errors and resolve like no errors occurred

All

Return both the errors and data, only works with rawRequest.

IgnoreOperationName

OperationName has been introduced to address issues reported here Support operation name, However, on certain occasions this information may not be needed in requests. In such cases, you might consider ignoring operationName to avoid the extraction steps currently performed by a parsing operation when the document is provided in string format.

By default the GraphQLClient tries to extract the operationName from the document. You can define excludeOperationName in the constructor of GraphQLClient to avoid the extraction process if it is not needed. This can be useful if you don't use operationName and want to optimise queries by reducing the amount of computation as much as possible, especially if we are in a context where we are using documents in string format to reduce bundle size.

// example where the operation name is not ignored
const client = new GraphQLClient(endpoint, {
  method: 'POST',
})
// example in which the operation name is ignored
const client = new GraphQLClient(endpoint, {
  method: 'POST',
  excludeOperationName: true,
})

Knowledge Base

Why was the file upload feature taken away? Will it return?

In this issue we decided to make this library more stable and maintainable. In principal the feature is still in scope of this library and will make a return when we find time to do the feature right.

Why do I have to install graphql?

graphql-request uses methods exposed by the graphql package to handle some internal logic. On top of that, for TypeScript users, some types are used from the graphql package to provide better typings.

Do I need to wrap my GraphQL documents inside the gql template exported by graphql-request?

No. It is there for convenience so that you can get the tooling support like automatic formatting and syntax highlighting. You can use gql from graphql-tag if you need it for some reason too.

What sets graphql-request apart from other clients like Apollo, Relay, etc.?

graphql-request is the most minimal and simplest to use GraphQL client. It's perfect for small scripts or simple apps.

Compared to GraphQL clients like Apollo or Relay, graphql-request doesn't have a built-in cache and has no integrations for frontend frameworks. The goal is to keep the package and API as minimal as possible.

Project Stats

Package Installs

NPM Usage Trend of graphql-request

Repo Beats

Alt