GraphQL 改变了前端应用从 API 获取数据的方式。与多个返回固定数据结构的 REST 端点不同,GraphQL 允许你在一次查询中精确请求所需的数据。Apollo Client 是最流行的 React GraphQL 客户端,开箱即用地提供缓存、状态管理和开发者工具。本教程将带你从零开始使用 Apollo Client 构建完整的 React 应用。
什么是 GraphQL?
GraphQL 是一种 API 查询语言和执行查询的运行时。由 Facebook 于 2012 年开发并于 2015 年开源,它提供对 API 中数据的完整且可理解的描述,赋予客户端精确请求所需数据的能力,并使 API 更容易随时间演进。
与 REST 每个端点返回固定数据结构不同,GraphQL 暴露单个端点。客户端发送描述所需数据精确形状的查询,服务器精确返回该形状的响应。这消除了过度获取(获取多余数据)和不足获取(需要多次请求才能组装所需数据)的问题。
GraphQL vs REST:关键区别
| Aspect | REST | GraphQL |
|---|---|---|
| Endpoints | Multiple (GET /users, GET /posts) | Single (/graphql) |
| Data Fetching | Fixed response shape | Client specifies exact fields |
| Over-fetching | Common | Eliminated |
| Under-fetching | Requires multiple requests | Single query gets all data |
| Versioning | URL versioning (v1, v2) | Schema evolution, no versioning needed |
| Caching | HTTP caching built-in | Requires client-side cache (Apollo) |
| Tooling | Mature (Swagger, Postman) | Strong (GraphiQL, Apollo DevTools) |
| Learning Curve | Low | Medium |
在 React 中设置 Apollo Client
Apollo Client 提供管理 React 应用中 GraphQL 数据所需的一切:声明式数据获取 API、智能的规范化缓存和出色的开发者工具。
// 1. 安装依赖
// npm install @apollo/client graphql
// 2. src/lib/apollo-client.ts
import {
ApolloClient,
InMemoryCache,
createHttpLink,
from,
} from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { onError } from '@apollo/client/link/error';
// API 的 HTTP 连接
const httpLink = createHttpLink({
uri: process.env.NEXT_PUBLIC_GRAPHQL_URL || 'http://localhost:4000/graphql',
});
// 认证中间件
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('auth_token');
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
};
});
// 错误处理
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ message, path }) => {
console.error(`[GraphQL 错误]: ${message}, 路径: ${path}`);
});
}
});
// 创建 Apollo Client 实例
export const client = new ApolloClient({
link: from([errorLink, authLink, httpLink]),
cache: new InMemoryCache(),
});编写 GraphQL 查询
GraphQL 查询描述你需要的精确数据形状。Apollo Client 提供 useQuery hook,自动处理加载状态、错误和缓存。
// src/graphql/queries.ts
import { gql } from '@apollo/client';
export const GET_POSTS = gql`
query GetPosts($limit: Int!, $offset: Int!) {
posts(limit: $limit, offset: $offset) {
id
title
slug
excerpt
publishedAt
author { id name avatar }
tags
}
postsCount
}
`;在组件中使用 useQuery
useQuery hook 是使用 Apollo Client 获取数据的主要方式。它返回 loading、error 和 data 状态,以及用于分页的 refetch 和 fetchMore 函数。
// src/components/PostList.tsx
'use client';
import { useQuery } from '@apollo/client';
import { GET_POSTS } from '@/graphql/queries';
export function PostList() {
const { loading, error, data, fetchMore } = useQuery(GET_POSTS, {
variables: { limit: 10, offset: 0 },
});
if (error) return <div>错误: {error.message}</div>;
if (loading && !data) return <div>加载中...</div>;
return (
<div>
{data.posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}变更操作:创建和更新数据
变更(Mutation)是修改服务端数据的 GraphQL 操作。Apollo Client 提供 useMutation hook,支持乐观更新、缓存操作和自动重新获取。
// src/graphql/mutations.ts
import { gql } from '@apollo/client';
export const CREATE_POST = gql`
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
id title slug content publishedAt
author { id name }
}
}
`;
// 在组件中使用
import { useMutation } from '@apollo/client';
const [createPost, { loading }] = useMutation(CREATE_POST, {
update(cache, { data }) {
cache.modify({
fields: {
posts(existing = []) {
const newRef = cache.writeFragment({
data: data.createPost,
fragment: gql`fragment NewPost on Post { id title }`,
});
return [newRef, ...existing];
},
},
});
},
});Apollo 缓存:工作原理
Apollo Client 使用名为 InMemoryCache 的规范化内存缓存。当查询返回数据时,Apollo 将响应分解为单个对象,通过 __typename 和 id 标识每个对象,并将它们存储在扁平的查找表中。如果两个查询返回相同的对象(相同的 __typename + id),Apollo 只存储一次并更新所有引用它的组件。
这种规范化意味着当你通过变更操作更新一篇文章时,显示该文章的每个组件都会自动使用新数据重新渲染。无需手动状态同步。
// 缓存配置与类型策略
const cache = new InMemoryCache({
typePolicies: {
Post: {
keyFields: ['slug'], // 使用 slug 作为缓存键
},
Query: {
fields: {
posts: {
keyArgs: ['filter'],
merge(existing = [], incoming, { args }) {
const offset = args?.offset || 0;
const merged = existing.slice(0);
for (let i = 0; i < incoming.length; i++) {
merged[offset + i] = incoming[i];
}
return merged;
},
},
},
},
},
});使用订阅实现实时数据
GraphQL 订阅通过 WebSocket 连接实现实时更新。Apollo Client 支持订阅,让你的 UI 在服务端数据变化时自动更新。
// 设置 WebSocket 链接用于订阅
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
import { split } from '@apollo/client';
const wsLink = new GraphQLWsLink(
createClient({ url: 'ws://localhost:4000/graphql' })
);
// 分流:订阅走 WS,查询/变更走 HTTP
const splitLink = split(
({ query }) => {
const def = getMainDefinition(query);
return def.kind === 'OperationDefinition' && def.operation === 'subscription';
},
wsLink,
httpLink
);错误处理模式
健壮的错误处理对生产级 GraphQL 应用至关重要。Apollo 提供多层错误处理:链接层、查询层和组件层。
// 全面的错误处理
const { data, error } = useQuery(GET_POST, {
variables: { slug },
errorPolicy: 'all', // 返回部分数据和错误
onError: (error) => {
error.graphQLErrors?.forEach(err => {
if (err.extensions?.code === 'UNAUTHENTICATED') {
window.location.href = '/login';
}
});
},
});构建 GraphQL 服务器
完整的 Apollo Server 设置,包含类型定义、解析器、认证和数据源。
// server.ts — Apollo Server
import { ApolloServer } from '@apollo/server';
const typeDefs = `#graphql
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Query {
posts(limit: Int!, offset: Int!): [Post!]!
post(slug: String!): Post
}
type Mutation {
createPost(input: CreatePostInput!): Post!
}
`;
const server = new ApolloServer({ typeDefs, resolvers });Apollo Client 最佳实践
- 使用 Fragment 跨查询共享字段选择。这消除了重复并确保相同实体在多个查询中的一致性。
- 为每个查询设置适当的获取策略。不常变化的数据使用 cache-first,频繁更新的数据使用 cache-and-network,需要最新数据时使用 network-only。
- 为修改可见数据的变更操作实现乐观响应。这通过在服务器请求进行中立即更新缓存,使 UI 感觉是即时的。
- 使用 errorPolicy 选项优雅地处理部分错误。设置为 "all" 会同时返回数据和错误,让你在某些字段失败时仍能显示可用数据。
- 为分页配置类型策略。Apollo 不会自动合并分页结果,你需要在缓存类型策略中定义 merge 和 read 函数。
- 将查询与组件共置。将 GraphQL 操作放在使用它们的组件的同一文件或目录中,便于理解数据依赖关系。
- 使用 Apollo DevTools 进行调试。浏览器扩展展示你的缓存内容、活跃查询和变更历史,帮助诊断数据问题。
- 使用 graphql-codegen 等工具从 GraphQL schema 生成 TypeScript 类型。这提供从 schema 到组件 props 的端到端类型安全。
试试我们相关的开发者工具
FAQ
我的项目应该用 GraphQL 还是 REST?
当你有复杂的、互相关联的数据,且多个客户端(Web、移动端)以不同方式消费时,或者过度获取和不足获取确实是性能问题时,使用 GraphQL。当你有简单的 CRUD 操作、需要 HTTP 缓存、或团队已经在 REST 上很高效时,使用 REST。很多团队两者都用:GraphQL 用于面向前端的 API,REST 用于内部微服务通信。
Apollo Client 能替代 Redux 做状态管理吗?
Apollo Client 可以替代 Redux 管理服务端状态(来自 API 的数据)。Apollo 缓存作为所有服务端数据的单一数据源,消除了用 Redux action 和 reducer 管理 API 响应的需要。对于客户端状态(UI 开关、表单数据、主题偏好),可以使用 Apollo 响应式变量、React context 或 Zustand 等轻量级库。
如何使用 Apollo 处理认证?
使用 Apollo Link 为每个请求附加认证头。setContext link 让你从存储中读取认证令牌并添加到请求头中。对于令牌刷新,使用 error link 捕获 401 响应、刷新令牌并自动重试失败的请求。
GraphQL 比 REST 慢吗?
GraphQL 的查询解析和验证比 REST 多了一小部分开销。但 GraphQL 通常通过减少网络请求数量(一次查询代替多次 REST 调用)和消除过度获取来提升整体性能。对大多数应用来说,网络节省超过了解析开销。使用持久化查询和响应缓存可以获得最佳性能。
Apollo Client 有什么替代方案?
主要替代方案有 urql(更轻量、基于插件)、Relay(Facebook 出品、有偏见的、编译器驱动的)和 TanStack Query 配合 graphql-request。Apollo Client 拥有最大的生态系统、最好的文档和最多的功能,但 urql 对于不需要 Apollo 全部功能的简单应用来说是一个不错的选择。