DevToolBox免费
博客

GraphQL + Apollo React 完整教程

14 分钟作者 DevToolBox

GraphQL 改变了前端应用从 API 获取数据的方式。与多个返回固定数据结构的 REST 端点不同,GraphQL 允许你在一次查询中精确请求所需的数据。Apollo Client 是最流行的 React GraphQL 客户端,开箱即用地提供缓存、状态管理和开发者工具。本教程将带你从零开始使用 Apollo Client 构建完整的 React 应用。

什么是 GraphQL?

GraphQL 是一种 API 查询语言和执行查询的运行时。由 Facebook 于 2012 年开发并于 2015 年开源,它提供对 API 中数据的完整且可理解的描述,赋予客户端精确请求所需数据的能力,并使 API 更容易随时间演进。

与 REST 每个端点返回固定数据结构不同,GraphQL 暴露单个端点。客户端发送描述所需数据精确形状的查询,服务器精确返回该形状的响应。这消除了过度获取(获取多余数据)和不足获取(需要多次请求才能组装所需数据)的问题。

GraphQL vs REST:关键区别

AspectRESTGraphQL
EndpointsMultiple (GET /users, GET /posts)Single (/graphql)
Data FetchingFixed response shapeClient specifies exact fields
Over-fetchingCommonEliminated
Under-fetchingRequires multiple requestsSingle query gets all data
VersioningURL versioning (v1, v2)Schema evolution, no versioning needed
CachingHTTP caching built-inRequires client-side cache (Apollo)
ToolingMature (Swagger, Postman)Strong (GraphiQL, Apollo DevTools)
Learning CurveLowMedium

在 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 全部功能的简单应用来说是一个不错的选择。

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

GQLJSON to GraphQL

相关文章

GraphQL vs REST API:2026 年该用哪个?

深入比较 GraphQL 和 REST API,附代码示例。学习架构差异、数据获取模式、缓存策略,以及何时选择哪种方案。

React Hooks 完全指南:useState、useEffect 和自定义 Hooks

通过实际示例掌握 React Hooks。学习 useState、useEffect、useContext、useReducer、useMemo、useCallback、自定义 Hooks 和 React 18+ 并发 Hooks。