Skip to content

GraphQL - Apollo Client

Apollo Client 是一个功能全面的 JavaScript 状态管理库,它使你能够通过 GraphQL 管理本地数据和远程数据。它是构建与 GraphQL API 交互的客户端应用的最流行选择,尤其在 React 生态系统中,但它也支持 Angular、Vue 和原生 JS 等其他框架。

虽然你可以使用基本的 fetch 调用,但 Apollo Client 提供了显著的优势,例如缓存(caching)、通过 Hook(钩子)实现的 UI 集成、错误处理、分页(pagination)、乐观 UI(optimistic UI)等。

  • **ApolloClient 实例:**管理配置、网络请求和缓存的中心对象。
  • **ApolloProvider:**一个 React 组件,它使用 React 的 Context API 将 ApolloClient 实例提供给应用树中的所有组件。
  • **gql Tag:**一个 JavaScript 模板字面量标签,用于将 GraphQL 查询字符串解析为标准的 AST(抽象语法树)格式。
  • **Hook(useQuery、useMutation、useSubscription、useLazyQuery):**React Hook,它们提供了一种声明式的方式在函数组件中获取、修改和订阅 GraphQL 数据。
  • **InMemoryCache:**默认的强大缓存解决方案。它根据对象标识符(__typename 和 id 或通过自定义键)对数据进行规范化,并将其存储在一个扁平结构中,从而实现高效缓存和自动 UI 更新。
  • **Apollo Link:**一个用于控制 GraphQL 操作网络流程的系统。Link 可以链式组合,以添加认证头部(authentication headers)、错误处理、重试(retries)、批处理(batching)等功能。

让我们回顾一下设置过程,重点关注关键配置方面。

npm install @apollo/client graphql

在你的应用入口文件(例如,src/main.jsx 或 src/index.js)中:

// src/main.jsx or index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import {
ApolloClient,
InMemoryCache,
ApolloProvider,
createHttpLink // 通常用于更复杂的 Link 设置
} from '@apollo/client';
// import { setContext } from '@apollo/link-context'; // 如果使用认证头部
// 1. 配置 HTTP Link (指向你的 GraphQL 服务器)
const httpLink = createHttpLink({
uri: 'http://localhost:9000/graphql', // 替换为你的服务器 URL
});
// 2. (可选) 配置认证 Link (示例)
// const authLink = setContext((_, { headers }) => {
// const token = localStorage.getItem('authToken');
// return {
// headers: {
// ...headers,
// authorization: token ? `Bearer ${token}` : '',
// }
// }
// });
// 3. 配置缓存
const cache = new InMemoryCache({
// 可选的缓存配置
// typePolicies: { ... } // 用于高级缓存控制
});
// 4. 创建 Apollo Client 实例
const client = new ApolloClient({
// 如果需要,组合 Link (例如 authLink 在 httpLink 之前)
// link: authLink.concat(httpLink),
link: httpLink, // 如果没有 authLink,直接使用 httpLink
cache: cache,
});
// 5. 使用 ApolloProvider 包裹你的应用
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</React.StrictMode>
);

这个设置使用服务器端点和缓存初始化客户端,然后使客户端在整个应用中可用。

useQuery Hook 是获取数据的主要方式。

import { useQuery, gql } from '@apollo/client';
const GET_STUDENTS = gql`
query GetAllStudents {
students {
id
firstName
lastName
college {
id
name
}
}
}
`;
function StudentList() {
const { loading, error, data, refetch } = useQuery(GET_STUDENTS, {
// --- 常用选项 ---
// variables: { /* ... */ }, // 如果查询接受变量
// pollInterval: 5000, // 每隔 5 秒重新获取数据
// fetchPolicy: 'cache-and-network', // 缓存/网络交互策略
// notifyOnNetworkStatusChange: true, // 重新获取时显示加载指示器很有用
// onError: (err) => { console.error("Query error:", err); },
// onCompleted: (d) => { console.log("Query completed:", d); }
});
if (loading) return <p>Loading students...</p>;
if (error) return <p>Error loading students: {error.message}</p>;
return (
<div>
<h2>Student List</h2>
<button onClick={() => refetch()}>Refetch Students</button>
<ul>
{data.students.map(student => (
<li key={student.id}>
{student.firstName} {student.lastName} (College: {student.college?.name || 'N/A'})
</li>
))}
</ul>
</div>
);
}

主要返回值和选项:

  • loading:布尔值,当查询正在进行时为 true。
  • error:如果请求失败,则为 ApolloError 对象。
  • data:一个与查询形状匹配的对象,包含结果(加载前为 undefined)。
  • refetch:一个手动重新执行查询的函数。
  • fetchPolicy:控制 Apollo 如何与缓存和网络交互(例如,cache-first、network-only、cache-and-network、no-cache)。详情请参阅文档。默认值:cache-first。
  • pollInterval:在指定的时间间隔(以毫秒为单位)自动重新获取查询。

useMutation Hook 用于发送 mutation。

import React, { useState } from 'react';
import { useMutation, gql } from '@apollo/client';
const CREATE_STUDENT = gql`
mutation AddStudent($input: CreateStudentInput!) {
createStudent(input: $input) {
# 请求创建的学生所需字段,用于缓存更新
id
firstName
lastName
collegeId
# 也可以请求相关数据
# college { id name }
}
}
`;
// 假设 useQuery 示例中的 GET_STUDENTS 查询已存在
function AddStudentForm() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [collegeId, setCollegeId] = useState('col-101'); // Example default
const [addStudent, { data, loading, error }] = useMutation(CREATE_STUDENT, {
// --- 常用选项 ---
// 选项 1:Mutation 后重新获取查询
// refetchQueries: [{ query: GET_STUDENTS }],
// 选项 2:直接更新缓存(更复杂但 UI 更新更快)
update(cache, { data: { createStudent: newStudent } }) {
try {
// 从缓存中读取现有学生列表
const existingData = cache.readQuery({ query: GET_STUDENTS });
if (existingData && newStudent) {
// 将更新后的列表写回缓存
cache.writeQuery({
query: GET_STUDENTS,
data: { students: [...existingData.students, newStudent] },
});
}
} catch (e) {
console.warn("Cache update failed (query likely not in cache yet):", e);
}
},
onError: (err) => { console.error("Mutation error:", err); },
// onCompleted: (d) => { console.log("Mutation completed:", d); }
});
const handleSubmit = (e) => {
e.preventDefault();
addStudent({
variables: {
input: { firstName, lastName, collegeId }
}
});
// 清空表单字段
setFirstName('');
setLastName('');
};
return (
<form onSubmit={handleSubmit}>
<h3>Add New Student</h3>
{/* firstName, lastName, collegeId 的输入字段 */}
<input value={firstName} onChange={e => setFirstName(e.target.value)} placeholder="First Name" required/>
<input value={lastName} onChange={e => setLastName(e.target.value)} placeholder="Last Name" />
<input value={collegeId} onChange={e => setCollegeId(e.target.value)} placeholder="College ID" required/>
<button type="submit" disabled={loading}>
{loading ? 'Adding...' : 'Add Student'}
</button>
{error && <p>Error adding student: {error.message}</p>}
{data && <p>Added: {data.createStudent.firstName} (ID: {data.createStudent.id})</p>}
</form>
);
}

关键方面:

  • useMutation 返回一个元组(tuple):[mutateFunction, { data, loading, error }]。
  • mutateFunction(此处为 addStudent)被调用以触发 mutation,通常传递一个 variables 对象。
  • **缓存更新:**Mutation 修改数据后,Apollo 缓存需要更新。常见策略有:
    • **refetchQueries:**最简单的方式。在 mutation 成功后重新运行指定的查询。如果查询很大,可能会很慢。
    • **直接缓存更新(update 函数):**更高效。使用 cache.readQuery、cache.writeQuery、cache.modify 等方法手动读写缓存。需要理解缓存结构,但提供即时 UI 更新。
    • **Mutation 返回值:**确保你的 mutation 返回包含 id 和 __typename 的修改后的对象,这样 Apollo 就可以潜在地自动更新该特定对象的缓存。
  • **useLazyQuery:**类似于 useQuery,但获取是通过调用 Hook 返回的函数手动触发的。
  • **useSubscription:**用于订阅来自服务器的实时更新(需要 WebSocket 设置)。
  • **分页(Pagination):**用于获取分页数据的辅助函数(例如 fetchMore)。
  • **乐观 UI(Optimistic UI):**在服务器确认之前,根据预期的 mutation 结果立即更新 UI。
  • **本地状态管理(Local State Management):**使用 Apollo Client 在管理远程数据的同时管理客户端状态。

Apollo Client 文档非常全面,提供了所有功能的详细指南: