@apollo/client
,React Native 应用可以高效地查询、修改和订阅数据。apollo3-cache-persist
支持离线功能。GraphQL 是一种 API 查询语言,允许客户端精确指定所需数据。它通过单一端点处理查询(Query)、修改(Mutation)和实时更新(Subscription),与 REST 的多端点设计形成对比。
您可以使用 Apollo Server 快速搭建 GraphQL 服务,定义模式(Schema)并实现解析器(Resolver)以连接数据源。
在 React Native 中,通过 @apollo/client
配置 ApolloProvider,使用 useQuery
和 useMutation
钩子获取和修改数据,处理加载状态和错误。
Apollo 的 InMemoryCache 和 apollo3-cache-persist
提供缓存持久化,支持离线操作。
GraphQL Code Generator 可根据模式和查询生成 TypeScript 类型,确保类型安全。
通过本文的代码示例,您可以构建一个任务管理应用,体验 GraphQL 和 Apollo 的强大功能。建议在实际项目中尝试这些技术,并结合团队需求优化配置。
在移动应用开发中,数据交互是核心环节。传统的 REST API 通常需要多个端点来获取不同数据,可能导致过量数据传输(over-fetching)或需要多次请求(under-fetching)。GraphQL 通过单一端点和灵活的查询机制解决了这些问题,允许客户端精确指定所需数据结构。这对于 React Native 应用尤其重要,因为移动设备对网络效率和性能敏感。
Apollo 客户端是 GraphQL 生态系统中的关键工具,为 React Native 提供了强大的功能,包括数据查询、修改、缓存管理和实时更新。与传统的 REST 和 Redux 组合相比,Apollo 客户端减少了状态管理复杂性,并通过内置缓存优化了性能。此外,结合 TypeScript 和 GraphQL Code Generator,可以实现类型安全,进一步提升开发效率。
本文的目标是通过一个“任务管理”应用示例,展示如何在 React Native 中使用 GraphQL 和 Apollo 客户端。您将学习如何搭建 GraphQL 后端、集成 Apollo 客户端、管理离线数据、实现类型安全,并了解其与 REST 的优劣对比。无论您是 GraphQL 新手还是希望深入了解其在 React Native 中的应用,本文都将提供全面的指导。
GraphQL 是一种由 Facebook 开发并于 2015 年开源的 API 查询语言和运行时环境。它允许客户端通过单一端点请求精确的数据结构,而无需依赖服务器预定义的响应格式。GraphQL 的核心优势在于其灵活性和效率,特别适合移动应用开发。
与传统的 REST API 不同,GraphQL 使用强类型系统(Schema)定义数据结构,客户端可以根据需求查询特定字段。GraphQL 支持三种主要操作:
REST 和 GraphQL 是两种常见的 API 设计风格。以下是它们的主要对比:
特性 | REST | GraphQL |
---|---|---|
数据获取 | 多个端点(如 /users/ 、/users/ ) |
单一端点,客户端指定数据结构 |
数据量控制 | 服务器定义响应结构,可能过量或不足 | 客户端精确请求所需字段 |
请求次数 | 可能需要多次请求获取完整数据 | 一次查询获取所有数据 |
实时更新 | 需额外实现 WebSocket 或轮询 | 内置订阅支持实时更新 |
版本管理 | 通常通过 URL 版本(如 /v1/users ) |
通过模式演进避免版本化 |
复杂性 | 客户端简单,服务器设计复杂 | 服务器简单,客户端查询复杂 |
在 REST 中,客户端无法控制响应数据的结构。例如,获取用户信息可能返回包括生日、地址等无关字段,导致过量数据传输。如果需要用户的帖子列表,则需额外请求 /users/
,增加网络开销。
GraphQL 允许客户端通过查询指定所需字段。例如:
query {
user(id: "1") {
name
posts {
title
}
}
}
此查询仅返回用户的姓名和帖子标题,减少数据传输量。此外,GraphQL 的强类型系统和模式定义使前后端开发更独立,客户端无需等待后端调整端点。
查询用于读取数据。例如,获取任务列表:
query GetTasks {
tasks {
id
title
completed
}
}
变更用于修改数据。例如,添加新任务:
mutation AddTask($title: String!) {
addTask(title: $title) {
id
title
completed
}
}
订阅用于实时更新。例如,监听新任务添加:
subscription OnTaskAdded {
taskAdded {
id
title
completed
}
}
这些操作将在后续的 Apollo 客户端集成中详细展示。
要使用 GraphQL,您需要一个后端服务提供 GraphQL API。本节将介绍如何使用 Apollo Server 快速搭建一个简单的 GraphQL 服务,并简要提及 Hasura 和 Prisma 作为替代方案。
Apollo Server 是一个开源的 GraphQL 服务器,支持 Node.js 环境,易于配置和扩展。以下是搭建步骤。
创建一个新的 Node.js 项目:
mkdir graphql-server
cd graphql-server
npm init -y
安装 Apollo Server 和 GraphQL:
npm install @apollo/server graphql
在 schema.js
中定义 GraphQL 模式:
const { gql } = require('@apollo/server');
const typeDefs = gql`
type Task {
id: ID!
title: String!
completed: Boolean!
}
type Query {
tasks: [Task]
}
type Mutation {
addTask(title: String!): Task
}
`;
module.exports = typeDefs;
在 resolvers.js
中定义解析器,处理查询和变更逻辑:
const tasks = [
{ id: '1', title: '学习 GraphQL', completed: false },
{ id: '2', title: '构建 React Native 应用', completed: true },
];
const resolvers = {
Query: {
tasks: () => tasks,
},
Mutation: {
addTask: (_, { title }) => {
const newTask = { id: String(tasks.length + 1), title, completed: false };
tasks.push(newTask);
return newTask;
},
},
};
module.exports = resolvers;
在 index.js
中创建 Apollo Server 实例并启动:
const { ApolloServer } = require('@apollo/server');
const { startStandaloneServer } = require('@apollo/server/standalone');
const typeDefs = require('./schema');
const resolvers = require('./resolvers');
const server = new ApolloServer({
typeDefs,
resolvers,
});
startStandaloneServer(server, {
listen: { port: 4000 },
}).then(({ url }) => {
console.log(`服务器运行在 ${url}`);
});
运行服务器:
node index.js
访问 [invalid url, do not cite]
,使用 GraphiQL 测试查询:
query {
tasks {
id
title
completed
}
}
Hasura 是一个开源引擎,可连接现有数据库(如 PostgreSQL)并自动生成 GraphQL API。它适合快速原型开发,无需手动编写解析器。
Prisma 是一个现代 ORM,支持 GraphQL 服务器搭建。它通过 Prisma Schema 定义数据模型,自动生成类型安全的 API。
Apollo 客户端是 React Native 中使用 GraphQL 的首选工具,提供数据查询、变更、缓存管理和实时更新功能。
在 React Native 项目中安装 Apollo 客户端:
npm install @apollo/client graphql
在 apolloClient.js
中创建客户端实例:
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: '[invalid url, do not cite]',
cache: new InMemoryCache(),
});
export default client;
在 App.js
中使用 ApolloProvider
包装应用:
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import client from './apolloClient';
import TaskList from './TaskList';
const App = () => {
return (
<ApolloProvider client={client}>
<TaskList />
</ApolloProvider>
);
};
export default App;
在 TaskList.js
中使用 useQuery
获取任务列表:
import React from 'react';
import { View, Text, FlatList } from 'react-native';
import { useQuery, gql } from '@apollo/client';
const GET_TASKS = gql`
query GetTasks {
tasks {
id
title
completed
}
}
`;
const TaskList = () => {
const { loading, error, data } = useQuery(GET_TASKS);
if (loading) return <Text>加载中...</Text>;
if (error) return <Text>错误: {error.message}</Text>;
return (
<FlatList
data={data.tasks}
renderItem={({ item }) => <Text>{item.title}</Text>}
keyExtractor={item => item.id}
/>
);
};
export default TaskList;
在 AddTask.js
中使用 useMutation
添加任务:
import React, { useState } from 'react';
import { View, TextInput, Button } from 'react-native';
import { useMutation, gql } from '@apollo/client';
const ADD_TASK = gql`
mutation AddTask($title: String!) {
addTask(title: $title) {
id
title
completed
}
}
`;
const GET_TASKS = gql`
query GetTasks {
tasks {
id
title
completed
}
}
`;
const AddTask = () => {
const [title, setTitle] = useState('');
const [addTask, { data, loading, error }] = useMutation(ADD_TASK, {
refetchQueries: [{ query: GET_TASKS }],
});
const handleAdd = () => {
addTask({ variables: { title } });
setTitle('');
};
return (
<View>
<TextInput value={title} onChangeText={setTitle} placeholder="任务标题" />
<Button title="添加任务" onPress={handleAdd} disabled={loading} />
{error && <Text>错误: {error.message}</Text>}
</View>
);
};
export default AddTask;
通过 refetchQueries
,在添加任务后自动重新获取任务列表。另一种方法是手动更新缓存:
addTask({
variables: { title },
update(cache, { data: { addTask } }) {
const { tasks } = cache.readQuery({ query: GET_TASKS });
cache.writeQuery({
query: GET_TASKS,
data: { tasks: [...tasks, addTask] },
});
},
});
为用户提供友好的错误提示,并支持重试:
const TaskList = () => {
const { loading, error, data, refetch } = useQuery(GET_TASKS);
if (loading) return <Text>加载中...</Text>;
if (error) {
return (
<View>
<Text>错误: {error.message}</Text>
<Button title="重试" onPress={refetch} />
</View>
);
}
return (
<FlatList
data={data.tasks}
renderItem={({ item }) => <Text>{item.title}</Text>}
keyExtractor={item => item.id}
/>
);
};
Apollo 客户端的缓存机制是其核心优势之一,支持离线操作和性能优化。
InMemoryCache
是 Apollo 的默认缓存,基于数据类型和 ID 规范化存储。例如,任务数据按 Task:id
存储,允许多个查询共享缓存。
cache-first
(默认)、network-only
或 cache-and-network
控制缓存行为:useQuery(GET_TASKS, { fetchPolicy: 'cache-and-network' });
apollo3-cache-persist
将缓存持久化到本地存储,支持离线操作。
npm install apollo3-cache-persist @react-native-async-storage/async-storage
在 apolloClient.js
中配置持久化:
import { InMemoryCache } from '@apollo/client';
import { persistCache } from 'apollo3-cache-persist';
import AsyncStorage from '@react-native-async-storage/async-storage';
const cache = new InMemoryCache();
persistCache({
cache,
storage: AsyncStorage,
});
const client = new ApolloClient({
uri: '[invalid url, do not cite]',
cache,
});
export default client;
缓存持久化后,应用可在离线状态下显示缓存数据。网络恢复时,重新查询可更新数据。
id
)。GraphQL 订阅通过 WebSocket 提供实时更新,适合任务通知等场景。
在 Apollo Server 中,使用 subscriptions-transport-ws
支持订阅:
npm install subscriptions-transport-ws
在 schema.js
中添加订阅:
const { gql } = require('@apollo/server');
const typeDefs = gql`
type Task {
id: ID!
title: String!
completed: Boolean!
}
type Query {
tasks: [Task]
}
type Mutation {
addTask(title: String!): Task
}
type Subscription {
taskAdded: Task
}
`;
module.exports = typeDefs;
在 resolvers.js
中添加订阅逻辑:
const { PubSub } = require('apollo-server');
const pubsub = new PubSub();
const TASK_ADDED = 'TASK_ADDED';
const resolvers = {
Query: {
tasks: () => tasks,
},
Mutation: {
addTask: (_, { title }) => {
const newTask = { id: String(tasks.length + 1), title, completed: false };
tasks.push(newTask);
pubsub.publish(TASK_ADDED, { taskAdded: newTask });
return newTask;
},
},
Subscription: {
taskAdded: {
subscribe: () => pubsub.asyncIterator([TASK_ADDED]),
},
},
};
module.exports = resolvers;
更新 index.js
支持订阅(需额外配置,参考 Apollo 文档)。
在 React Native 中,使用 useSubscription
接收实时更新:
import React from 'react';
import { View, Text } from 'react-native';
import { useSubscription, gql } from '@apollo/client';
const TASK_ADDED = gql`
subscription OnTaskAdded {
taskAdded {
id
title
completed
}
}
`;
const TaskSubscription = () => {
const { data, loading } = useSubscription(TASK_ADDED);
if (loading) return null;
return <Text>新任务: {data.taskAdded.title}</Text>;
};
export default TaskSubscription;
在 apolloClient.js
中添加 WebSocket 链接:
import { ApolloClient, InMemoryCache, split } from '@apollo/client';
import { WebSocketLink } from '@apollo/client/link/ws';
import { HttpLink } from '@apollo/client/link/http';
import { getMainDefinition } from '@apollo/client/utilities';
const httpLink = new HttpLink({
uri: '[invalid url, do not cite]',
});
const wsLink = new WebSocketLink({
uri: 'ws://localhost:4000/graphql',
options: {
reconnect: true,
},
});
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
);
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
export default client;
在选择 GraphQL 和 Apollo 客户端还是 REST 和 Redux 时,需根据项目需求权衡优劣。
使用 REST 获取任务列表:
import axios from 'axios';
import { createSlice } from '@reduxjs/toolkit';
const taskSlice = createSlice({
name: 'tasks',
initialState: { list: [], loading: false, error: null },
reducers: {
fetchTasksStart(state) {
state.loading = true;
},
fetchTasksSuccess(state, action) {
state.list = action.payload;
state.loading = false;
},
fetchTasksFailure(state, action) {
state.error = action.payload;
state.loading = false;
},
},
});
export const { fetchTasksStart, fetchTasksSuccess, fetchTasksFailure } = taskSlice.actions;
export const fetchTasks = () => async (dispatch) => {
dispatch(fetchTasksStart());
try {
const response = await axios.get('[invalid url, do not cite]
dispatch(fetchTasksSuccess(response.data));
} catch (error) {
dispatch(fetchTasksFailure(error.message));
}
};
export default taskSlice.reducer;
TypeScript 与 GraphQL 的结合通过类型安全提升开发效率。GraphQL Code Generator 可根据模式和查询生成 TypeScript 类型。
npm install --save-dev @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo
在项目根目录创建 codegen.yml
:
schema: '[invalid url, do not cite]'
documents: 'src/**/*.graphql'
generates:
src/generated/graphql.tsx:
plugins:
- typescript
- typescript-operations
- typescript-react-apollo
config:
withHooks: true
在 src/queries/getTasks.graphql
中定义查询:
query GetTasks {
tasks {
id
title
completed
}
}
运行生成命令:
npx graphql-codegen
生成的文件 src/generated/graphql.tsx
包含类型和钩子。
在组件中使用生成的钩子:
import React from 'react';
import { View, Text, FlatList } from 'react-native';
import { useGetTasksQuery } from './generated/graphql';
const TaskList = () => {
const { data, loading, error } = useGetTasksQuery();
if (loading) return <Text>加载中...</Text>;
if (error) return <Text>错误: {error.message}</Text>;
return (
<FlatList
data={data?.tasks}
renderItem={({ item }) => <Text>{item.title}</Text>}
keyExtractor={item => item.id}
/>
);
};
export default TaskList;
以下是一个完整的任务管理应用示例,结合查询、变更和订阅:
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { ApolloProvider } from '@apollo/client';
import client from './apolloClient';
import TaskList from './TaskList';
import AddTask from './AddTask';
const Stack = createStackNavigator();
const App = () => {
return (
<ApolloProvider client={client}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="TaskList" component={TaskList} />
<Stack.Screen name="AddTask" component={AddTask} />
</Stack.Navigator>
</NavigationContainer>
</ApolloProvider>
);
};
export default App;
通过本文,您掌握了在 React Native 中使用 GraphQL 和 Apollo 客户端的完整流程,包括搭建后端、集成客户端、管理缓存、实现离线支持和类型安全。以下是关键总结:
apollo3-cache-persist
实现缓存持久化。