GraphQL - React 集成
GraphQL - 现代 React 与 Apollo Client 集成
Section titled “GraphQL - 现代 React 与 Apollo Client 集成”React 是一个流行的 JavaScript 库,用于构建用户界面(UI)。本章将讲解如何使用 Apollo Client 将 GraphQL 集成到现代 React 应用中。Apollo Client 是 React 生态系统中用于 GraphQL 客户端操作的事实标准库。
为什么选择 Apollo Client?
Section titled “为什么选择 Apollo Client?”虽然你可以使用标准的 fetch 调用与 GraphQL API 交互,但像 Apollo Client 这样的库提供了显著的优势:
- 声明式数据获取:在 UI 组件旁边定义数据需求。
- 智能缓存:自动规范化缓存数据,减少冗余的网络请求。
- UI 集成:使用 Hooks 无缝集成 React 的状态和生命周期。
- 开发者工具:优秀的浏览器扩展,用于检查查询(queries)、变体(mutations)和缓存。
- 错误处理:强大的机制处理 GraphQL 和网络错误。
- 状态管理:可以同时管理远程和本地应用状态。
- 一个正在运行的 GraphQL 服务器(我们将搭建一个基础服务器)。
- 已安装 Node.js 和 npm/yarn。
- 对 React 有基础了解(函数组件和 Hooks)。
搭建 GraphQL 服务器(现代化)
Section titled “搭建 GraphQL 服务器(现代化)”首先,让我们使用 Apollo Server 4 和 Express 搭建一个最小的 GraphQL 服务器。这个服务器将为我们的 React 客户端提供数据。
第一步:项目设置和依赖
Section titled “第一步:项目设置和依赖”创建一个新文件夹(例如 react-graphql-server),并初始化一个 Node.js 项目:
mkdir react-graphql-servercd react-graphql-servernpm init -ynpm install @apollo/server graphql express cors body-parser我们安装了 Apollo Server、GraphQL.js、Express、CORS 中间件和 body-parser。
第二步:定义 Schema (schema.graphql)
Section titled “第二步:定义 Schema (schema.graphql)”创建一个 schema.graphql 文件:
type Query { greeting: String sayHello(name: String!): String}这个 Schema 定义了两个简单的查询:greeting 和 sayHello。
第三步:创建 Resolvers (resolvers.js)
Section titled “第三步:创建 Resolvers (resolvers.js)”创建一个 resolvers.js 文件:
const resolvers = { Query: { greeting: () => 'Hello GraphQL World from Apollo Server!', // The 'args' object contains the arguments passed to the query field // 'args' 对象包含传递给查询字段的参数 sayHello: (_, { name }) => `Hi ${name}! GraphQL server says Hello to you!`, },};
export default resolvers;// Or if using CommonJS: module.exports = resolvers;// 如果使用 CommonJS: module.exports = resolvers;这些函数解析(resolve)了 Schema 中定义的查询。注意使用对象解构 (_, { name }) 来访问参数的现代用法。
第四步:设置 Apollo Server (server.js)
Section titled “第四步:设置 Apollo Server (server.js)”创建一个 server.js 文件:
import { ApolloServer } from '@apollo/server';import { expressMiddleware } from '@apollo/server/express4';import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';import express from 'express';import http from 'http';import cors from 'cors';import bodyParser from 'body-parser';import { readFileSync } from 'fs';
// Using dynamic import for resolvers if using ES modules and resolvers.js uses module.exports// 如果使用 ES modules 并且 resolvers.js 使用 module.exports,则使用动态导入// import resolvers from './resolvers.js';// If resolvers.js uses export default, regular import works.// 如果 resolvers.js 使用 export default,则常规导入即可。const resolvers = require('./resolvers'); // Use require if using CommonJS// 如果使用 CommonJS,则使用 require
// Load schema// 加载 Schemaconst typeDefs = readFileSync('./schema.graphql', { encoding: 'utf-8' });
async function startApolloServer() { const app = express(); const httpServer = http.createServer(app);
// Set up Apollo Server // 设置 Apollo Server const server = new ApolloServer({ typeDefs, resolvers, plugins: [ApolloServerPluginDrainHttpServer({ httpServer })], // introspection: true, // Optional: Enables schema introspection, default is true unless NODE_ENV is 'production' // introspection: true, // 可选:启用 Schema 自省,默认为 true,除非 NODE_ENV 是 'production' });
await server.start();
app.use( '/graphql', // The path where GraphQL endpoint will be available // GraphQL 接口的路径 cors(), // Enable CORS for all origins (adjust for production) // 启用 CORS(跨域资源共享),允许所有来源访问(生产环境请调整) bodyParser.json(), expressMiddleware(server, { context: async ({ req }) => ({ token: req.headers.token }), // Example context setup // 示例 Context 设置 }), );
const PORT = 9000; await new Promise((resolve) => httpServer.listen({ port: PORT }, resolve)); console.log(`🚀 Server ready at http://localhost:${PORT}/graphql`);}
startApolloServer();这使用 Express 搭建了 Apollo Server 4,集成了 Schema 和 Resolvers。它使用 expressMiddleware 来处理 GraphQL 请求。
第五步:运行服务器
Section titled “第五步:运行服务器”将启动脚本添加到你的 package.json 中:
"scripts": { "start": "node server.js" // Or use nodemon for development: "start": "nodemon server.js" // 或使用 nodemon 进行开发: "start": "nodemon server.js"}然后运行:
npm start服务器应该在 http://localhost:9000/graphql 运行。你可以在浏览器中打开这个 URL 来使用 Apollo Sandbox(GraphiQL 的现代替代品)测试你的查询:
query TestQuery { greeting sayHello(name: "React Developer")}使用 Apollo 搭建 React 客户端
Section titled “使用 Apollo 搭建 React 客户端”现在,让我们创建一个 React 应用,并使用 Apollo Client 连接到我们的 GraphQL 服务器。
第一步:创建一个 React 项目
Section titled “第一步:创建一个 React 项目”在 另一个 终端窗口中,创建一个 React 应用(推荐使用 Vite,它是 Create React App 的现代替代品,但 CRA 也可以):
# Using Vite (Recommended)# 使用 Vite (推荐)npm create vite@latest hello-world-client --template reactcd hello-world-clientnpm install
# OR Using Create React App# 或者使用 Create React App# npx create-react-app hello-world-client# cd hello-world-client第二步:安装 Apollo Client 依赖
Section titled “第二步:安装 Apollo Client 依赖”安装必要的 Apollo Client 包:
npm install @apollo/client graphql第三步:配置 Apollo Client
Section titled “第三步:配置 Apollo Client”在你的 React 应用主入口文件(例如 Vite 的 src/main.jsx 或 CRA 的 src/index.js)中,设置 Apollo Client 实例并使用 ApolloProvider 包装你的应用。
// src/main.jsx or src/index.js// src/main.jsx 或 src/index.jsimport React from 'react';import ReactDOM from 'react-dom/client';import App from './App';import './index.css'; // Or your main CSS file// 或你的主 CSS 文件
import { ApolloClient, InMemoryCache, ApolloProvider, gql } from '@apollo/client';
// Configure Apollo Client// 配置 Apollo Clientconst client = new ApolloClient({ uri: 'http://localhost:9000/graphql', // Your GraphQL server endpoint // 你的 GraphQL 服务器接口地址 cache: new InMemoryCache(),});
const root = ReactDOM.createRoot(document.getElementById('root'));root.render( <React.StrictMode> <ApolloProvider client={client}> <App /> </ApolloProvider> </React.StrictMode>);ApolloProvider 通过 React 的 Context API 使配置好的客户端在组件树中的所有组件中可用。
第四步:在组件中使用 useQuery Hook 获取数据
Section titled “第四步:在组件中使用 useQuery Hook 获取数据”修改你的 src/App.jsx (或 src/App.js) 文件,使用 Apollo Client 的 useQuery Hook 来获取数据。
// src/App.jsximport React, { useState } from 'react';import { useQuery, useLazyQuery, gql } from '@apollo/client';import './App.css'; // Example CSS import// 示例 CSS 导入
// Define GraphQL Queries using gql tag// 使用 gql 标签定义 GraphQL 查询const GET_GREETING = gql` query GetGreeting { greeting }`;
const SAY_HELLO = gql` query SayHello($name: String!) { sayHello(name: $name) }`;
function App() { const [nameInput, setNameInput] = useState('React User');
// useQuery: Executes the query immediately when the component mounts // useQuery: 组件挂载后立即执行查询 const { loading: greetingLoading, error: greetingError, data: greetingData } = useQuery(GET_GREETING);
// useLazyQuery: Returns a function to execute the query manually (e.g., on button click) // useLazyQuery: 返回一个函数,用于手动触发查询(例如,点击按钮时) const [loadHello, { loading: helloLoading, error: helloError, data: helloData }] = useLazyQuery(SAY_HELLO);
const handleSayHelloClick = () => { if (nameInput) { loadHello({ variables: { name: nameInput } }); } };
return ( <div className="App"> <header className="App-header"> <h1>Modern GraphQL + React</h1> {/* <h1>现代 GraphQL + React</h1> */} </header>
<section> <h2>Greeting Query (useQuery)</h2> {/* <h2>问候查询 (useQuery) */}</h2> {greetingLoading && <p>Loading greeting...</p>} {/* {greetingLoading && <p>正在加载问候语...</p>} */} {greetingError && <p>Error loading greeting: {greetingError.message}</p>} {/* {greetingError && <p>加载问候语出错: {greetingError.message}</p>} */} {greetingData && <p>Server says: {greetingData.greeting}</p>} {/* {greetingData && <p>服务器说: {greetingData.greeting}</p>} */} </section>
<hr />
<section> <h2>Say Hello Query (useLazyQuery)</h2> {/* <h2>说你好查询 (useLazyQuery) */}</h2> <label htmlFor="nameInput">Enter Name: </label> {/* <label htmlFor="nameInput">输入姓名: </label> */} <input id="nameInput" type="text" value={nameInput} onChange={(e) => setNameInput(e.target.value)} /> <button onClick={handleSayHelloClick} disabled={helloLoading || !nameInput}> {helloLoading ? 'Loading...' : 'Say Hello'} {/* {helloLoading ? '加载中...' : '说你好'} */} </button> {helloError && <p>Error saying hello: {helloError.message}</p>} {/* {helloError && <p>说你好出错: {helloError.message}</p>} */} {helloData && <p>Server says: {helloData.sayHello}</p>} {/* {helloData && <p>服务器说: {helloData.sayHello}</p>} */} </section> </div> );}
export default App;组件中的要点:
- 从
@apollo/client导入useQuery、useLazyQuery和gql。 - 使用
gql模板字面量标签定义查询。 useQuery(GET_GREETING)会自动获取问候语。useLazyQuery(SAY_HELLO)提供了一个loadHello函数,用于在需要时触发查询,并传递变量。- 这两个 Hook 都返回包含
loading、error和data状态的对象,方便处理不同的 UI 状态。 - 我们使用标准的 React
useState来管理输入框的值。
第五步:运行客户端应用
Section titled “第五步:运行客户端应用”确保你的 GraphQL 服务器仍在运行(在第一个终端窗口中)。在第二个终端窗口中(在 hello-world-client 目录下),启动 React 开发服务器:
npm run dev # If using Vite# 如果使用 Vite# OR# 或者npm start # If using Create React App# 如果使用 Create React App你的浏览器应该会打开 React 应用(Vite 通常是 http://localhost:5173,CRA 是 http://localhost:3000)。你应该看到自动加载的问候语,并且可以与“Say Hello”部分进行交互。
- Apollo Client 文档:https://www.apollographql.com/docs/react/
- React Hooks:https://react.dev/reference/react
- 使用
useMutationHook 处理变体(Mutations)。 - 使用 Apollo Client 管理本地状态。
- 使用 Apollo Link 实现认证流程。