前端 GraphQL 客户端:优雅地获取数据
常见误区
前端 GraphQL?这不是后端的事吗?
"REST API 就够了,为什么要用 GraphQL"——结果前端需要多次请求,数据冗余; "GraphQL 太复杂了,我学不会"——结果错过了更灵活的数据获取方式; "我直接用 fetch 请求 GraphQL,多简单"——结果缺少缓存、错误处理等功能。
实际上,GraphQL 不是后端的专利,前端也需要专业的客户端工具!
为什么你需要这个?
- 减少网络请求:一次请求获取所有需要的数据
- 数据精确:只获取需要的数据,避免冗余
- 类型安全:自动生成 TypeScript 类型
- 缓存优化:智能缓存,减少重复请求
- 开发效率:简化数据获取逻辑
原生 Fetch 方式的问题
// 反面教材:直接使用 fetch 请求 GraphQL
async function fetchGraphQL(query, variables) {
const response = await fetch('https://api.example.com/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables }),
});
const data = await response.json();
if (data.errors) {
console.error('GraphQL errors:', data.errors);
throw new Error('GraphQL request failed');
}
return data.data;
}
// 反面教材:重复请求相同数据
async function loadUserAndPosts() {
// 第一次请求用户信息
const { user } = await fetchGraphQL(`query GetUser($id: ID!) { user(id: $id) { id name email } }`, { id: 1 });
// 第二次请求用户的帖子
const { user: userWithPosts } = await fetchGraphQL(`query GetUserWithPosts($id: ID!) { user(id: $id) { id posts { id title content } } }`, { id: 1 });
return { user, posts: userWithPosts.posts };
}
推荐的专业客户端方案
// 正确的做法:使用 Apollo Client
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://api.example.com/graphql',
cache: new InMemoryCache(),
headers: { authorization: localStorage.getItem('token') || '' },
});
const GET_USER_WITH_POSTS = gql`
query GetUserWithPosts($id: ID!) {
user(id: $id) {
id
name
email
posts {
id
title
content
createdAt
}
}
}
`;
const CREATE_POST = gql`
mutation CreatePost($input: PostInput!) {
createPost(input: $input) {
id
title
content
createdAt
}
}
`;
import React from 'react';
import { useQuery, useMutation } from '@apollo/client';
function UserProfile({ userId }) {
const { loading, error, data, refetch } = useQuery(GET_USER_WITH_POSTS, {
variables: { id: userId },
fetchPolicy: 'cache-and-network',
});
const [createPost, { loading: creating }] = useMutation(CREATE_POST, {
update(cache, { data: { createPost } }) {
const { user } = cache.readQuery({ query: GET_USER_WITH_POSTS, variables: { id: userId } });
cache.writeQuery({
query: GET_USER_WITH_POSTS,
variables: { id: userId },
data: { user: { ...user, posts: [...user.posts, createPost] } },
});
},
});
if (loading) return <div>加载中...</div>;
if (error) return <div>错误:{error.message}</div>;
const handleCreatePost = async (title, content) => {
await createPost({ variables: { input: { title, content, userId } } });
};
return (
<div>
<h2>{data.user.name}</h2>
<p>{data.user.email}</p>
<h3>帖子</h3>
<ul>
{data.user.posts.map(post => (
<li key={post.id}>
<h4>{post.title}</h4>
<p>{post.content}</p>
<p>{post.createdAt}</p>
</li>
))}
</ul>
<button onClick={() => refetch()}>刷新</button>
<button onClick={() => handleCreatePost('新帖子', '帖子内容')} disabled={creating}>
{creating ? '创建中...' : '创建帖子'}
</button>
</div>
);
}
// 正确的做法:使用 URQL
import { createClient, gql } from 'urql';
const client = createClient({
url: 'https://api.example.com/graphql',
fetchOptions: () => ({ headers: { authorization: localStorage.getItem('token') || '' } }),
});
import React from 'react';
import { useQuery, useMutation } from 'urql';
function UserList() {
const [result, reexecuteQuery] = useQuery({
query: gql`query GetUsers { users { id name email } }`,
});
const { data, fetching, error } = result;
if (fetching) return <div>加载中...</div>;
if (error) return <div>错误:{error.message}</div>;
return (
<div>
<h2>用户列表</h2>
<ul>
{data.users.map(user => (
<li key={user.id}>{user.name} - {user.email}</li>
))}
</ul>
<button onClick={() => reexecuteQuery()}>刷新</button>
</div>
);
}
// 正确的做法:使用 Relay
import { Environment, Network, RecordSource, Store, useLazyLoadQuery, graphql } from 'relay-runtime';
function fetchQuery(operation, variables) {
return fetch('https://api.example.com/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json', authorization: localStorage.getItem('token') || '' },
body: JSON.stringify({ query: operation.text, variables }),
}).then(response => response.json());
}
const environment = new Environment({
network: Network.create(fetchQuery),
store: new Store(new RecordSource()),
});
const UserQuery = graphql`
query UserQuery($id: ID!) {
user(id: $id) {
id
name
email
posts {
edges {
node {
id
title
content
}
}
}
}
}
`;
function UserDetail({ userId }) {
const data = useLazyLoadQuery(UserQuery, { id: userId });
return (
<div>
<h2>{data.user.name}</h2>
<p>{data.user.email}</p>
<h3>帖子</h3>
<ul>
{data.user.posts.edges.map(edge => (
<li key={edge.node.id}>
<h4>{edge.node.title}</h4>
<p>{edge.node.content}</p>
</li>
))}
</ul>
</div>
);
}
技术总结
综上所述,前端 GraphQL 客户端不仅仅是发送请求,还包括缓存管理、错误处理、类型生成等功能。使用 Apollo Client、URQL 或 Relay 等专业的客户端工具可以大大简化前端代码,提高开发效率。
核心方案对比
- Apollo Client:功能强大,生态丰富,适合大型应用
- URQL:轻量级,API 简洁,适合中小型应用
- Relay:Facebook 开发,性能优异,适合大型应用
- 缓存管理:智能缓存,减少重复请求
- 类型安全:自动生成 TypeScript 类型
- 错误处理:统一的错误处理机制
- 变更管理:执行 GraphQL 变更并更新缓存
- 开发工具:GraphQL Playground、Apollo DevTools 等
前端 GraphQL 客户端,让数据获取变得更加优雅!

