Skip to content

GraphQL - React 集成

GraphQL - 现代 React 与 Apollo Client 集成

Section titled “GraphQL - 现代 React 与 Apollo Client 集成”

React 是一个流行的 JavaScript 库,用于构建用户界面(UI)。本章将讲解如何使用 Apollo Client 将 GraphQL 集成到现代 React 应用中。Apollo Client 是 React 生态系统中用于 GraphQL 客户端操作的事实标准库。

虽然你可以使用标准的 fetch 调用与 GraphQL API 交互,但像 Apollo Client 这样的库提供了显著的优势:

  • 声明式数据获取:在 UI 组件旁边定义数据需求。
  • 智能缓存:自动规范化缓存数据,减少冗余的网络请求。
  • UI 集成:使用 Hooks 无缝集成 React 的状态和生命周期。
  • 开发者工具:优秀的浏览器扩展,用于检查查询(queries)、变体(mutations)和缓存。
  • 错误处理:强大的机制处理 GraphQL 和网络错误。
  • 状态管理:可以同时管理远程和本地应用状态。
  • 一个正在运行的 GraphQL 服务器(我们将搭建一个基础服务器)。
  • 已安装 Node.js 和 npm/yarn。
  • 对 React 有基础了解(函数组件和 Hooks)。

首先,让我们使用 Apollo Server 4 和 Express 搭建一个最小的 GraphQL 服务器。这个服务器将为我们的 React 客户端提供数据。

创建一个新文件夹(例如 react-graphql-server),并初始化一个 Node.js 项目:

mkdir react-graphql-server
cd react-graphql-server
npm init -y
npm 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
// 加载 Schema
const 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 请求。

将启动脚本添加到你的 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")
}

现在,让我们创建一个 React 应用,并使用 Apollo Client 连接到我们的 GraphQL 服务器。

在 另一个 终端窗口中,创建一个 React 应用(推荐使用 Vite,它是 Create React App 的现代替代品,但 CRA 也可以):

# Using Vite (Recommended)
# 使用 Vite (推荐)
npm create vite@latest hello-world-client --template react
cd hello-world-client
npm install
# OR Using Create React App
# 或者使用 Create React App
# npx create-react-app hello-world-client
# cd hello-world-client

安装必要的 Apollo Client 包:

npm install @apollo/client graphql

在你的 React 应用主入口文件(例如 Vite 的 src/main.jsx 或 CRA 的 src/index.js)中,设置 Apollo Client 实例并使用 ApolloProvider 包装你的应用。

// src/main.jsx or src/index.js
// src/main.jsx 或 src/index.js
import 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 Client
const 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.jsx
import 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 来管理输入框的值。

确保你的 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”部分进行交互。