GraphQL - Apollo Client
GraphQL - Apollo Client 深入讲解
Section titled “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实例提供给应用树中的所有组件。 - **
gqlTag:**一个 JavaScript 模板字面量标签,用于将 GraphQL 查询字符串解析为标准的 AST(抽象语法树)格式。 - **Hook(
useQuery、useMutation、useSubscription、useLazyQuery):**React Hook,它们提供了一种声明式的方式在函数组件中获取、修改和订阅 GraphQL 数据。 - **
InMemoryCache:**默认的强大缓存解决方案。它根据对象标识符(__typename和id或通过自定义键)对数据进行规范化,并将其存储在一个扁平结构中,从而实现高效缓存和自动 UI 更新。 - **Apollo Link:**一个用于控制 GraphQL 操作网络流程的系统。Link 可以链式组合,以添加认证头部(authentication headers)、错误处理、重试(retries)、批处理(batching)等功能。
在 React 中设置 Apollo Client
Section titled “在 React 中设置 Apollo Client”让我们回顾一下设置过程,重点关注关键配置方面。
步骤 1:安装
Section titled “步骤 1:安装”npm install @apollo/client graphql步骤 2:客户端初始化与 Provider
Section titled “步骤 2:客户端初始化与 Provider”在你的应用入口文件(例如,src/main.jsx 或 src/index.js)中:
// src/main.jsx or index.jsimport 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 获取数据
Section titled “使用 useQuery 获取数据”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 修改数据
Section titled “使用 useMutation 修改数据”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 就可以潜在地自动更新该特定对象的缓存。
- **Mutation 返回值:**确保你的 mutation 返回包含
其他 Hook 和功能
Section titled “其他 Hook 和功能”- **
useLazyQuery:**类似于useQuery,但获取是通过调用 Hook 返回的函数手动触发的。 - **
useSubscription:**用于订阅来自服务器的实时更新(需要 WebSocket 设置)。 - **分页(Pagination):**用于获取分页数据的辅助函数(例如
fetchMore)。 - **乐观 UI(Optimistic UI):**在服务器确认之前,根据预期的 mutation 结果立即更新 UI。
- **本地状态管理(Local State Management):**使用 Apollo Client 在管理远程数据的同时管理客户端状态。
Apollo Client 文档非常全面,提供了所有功能的详细指南: