GraphQL 是一种用于 API 的查询语言,它的特点是可以精确地获取你所需要的数据,避免了传统 REST API 中可能存在的过度获取数据的问题。然而,在使用 GraphQL 进行数据查询时,有时会遇到环状引用的问题,这会导致查询中的某些字段被反复地重复获取,从而降低查询性能。
本文将介绍如何处理 GraphQL 查询中的环状引用问题,包括如何识别环状引用、如何避免环状引用、如何解决已经存在的环状引用等内容。
什么是环状引用?
在 GraphQL 查询中,如果两个对象之间存在相互引用,就会形成环状引用。例如,假设我们有一个查询,需要获取用户和他们的帖子,那么查询可能会是这样的:
// www.javascriptcn.com code example
{
users {
id
name
posts {
id
title
author {
id
name
posts {
id
title
author {
...
}
}
}
}
}
}在这个查询中,用户和帖子之间存在相互引用,因此会导致查询中的某些字段被反复地重复获取。
如何识别环状引用?
识别环状引用的方法很简单,只需要检查查询中是否存在相同的字段,如果存在,则说明可能存在环状引用。例如,在上面的查询中,我们可以看到 posts 和 author 字段被反复地重复获取,因此就可能存在环状引用。
如何避免环状引用?
避免环状引用的方法有很多,以下是一些常见的方法:
1. 限制查询深度
限制查询深度是一种简单有效的方法,可以避免查询中的某些字段被反复地重复获取。例如,在上面的查询中,我们可以限制查询深度,只获取每个用户的前几篇帖子,而不是获取所有帖子。这样可以避免查询中的 posts 和 author 字段被反复地重复获取。
2. 使用分页查询
使用分页查询也是一种有效的方法,可以避免查询中的某些字段被反复地重复获取。例如,在上面的查询中,我们可以使用分页查询,每次只获取一定数量的帖子,而不是获取所有帖子。这样可以避免查询中的 posts 和 author 字段被反复地重复获取。
3. 使用 @skip 和 @include 指令
使用 @skip 和 @include 指令可以根据条件动态地排除或包含某些字段。例如,在上面的查询中,我们可以使用 @skip 和 @include 指令,根据条件动态地排除或包含某些字段,避免查询中的 posts 和 author 字段被反复地重复获取。
如何解决已经存在的环状引用?
解决已经存在的环状引用的方法也有很多,以下是一些常见的方法:
1. 使用 DataLoader
DataLoader 是一个用于解决 GraphQL 查询中 N+1 问题的工具,它可以自动地批量加载数据,从而避免查询中的某些字段被反复地重复获取。例如,在上面的查询中,我们可以使用 DataLoader 批量加载每个用户的帖子,从而避免查询中的 posts 和 author 字段被反复地重复获取。
2. 使用缓存
使用缓存也是一种有效的方法,可以避免查询中的某些字段被反复地重复获取。例如,在上面的查询中,我们可以使用缓存,缓存每个用户的帖子,从而避免查询中的 posts 和 author 字段被反复地重复获取。
示例代码
以下是一个使用 DataLoader 解决环状引用问题的示例代码:
// www.javascriptcn.com code example
const { ApolloServer, gql } = require('apollo-server');
const DataLoader = require('dataloader');
const users = [
{
id: 1,
name: 'Alice',
postIds: [1, 2],
},
{
id: 2,
name: 'Bob',
postIds: [3, 4],
},
];
const posts = [
{
id: 1,
title: 'Post 1',
authorId: 1,
},
{
id: 2,
title: 'Post 2',
authorId: 1,
},
{
id: 3,
title: 'Post 3',
authorId: 2,
},
{
id: 4,
title: 'Post 4',
authorId: 2,
},
];
const typeDefs = gql`
type User {
id: ID!
name: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
author: User!
}
type Query {
users: [User!]!
posts: [Post!]!
}
`;
const resolvers = {
Query: {
users: () => users,
posts: () => posts,
},
User: {
posts: (parent, args, { loaders }) => loaders.post.loadMany(parent.postIds),
},
Post: {
author: (parent, args, { loaders }) => loaders.user.load(parent.authorId),
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
context: () => ({
loaders: {
user: new DataLoader((keys) => {
const result = keys.map((key) => users.find((user) => user.id === key));
return Promise.resolve(result);
}),
post: new DataLoader((keys) => {
const result = keys.map((key) => posts.filter((post) => post.authorId === key));
return Promise.resolve(result);
}),
},
}),
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});在这个示例代码中,我们使用 DataLoader 批量加载每个用户的帖子,从而避免查询中的 posts 和 author 字段被反复地重复获取。同时,我们使用缓存缓存每个用户的帖子,从而进一步提高查询性能。
Source: FunTeaLearn,Please indicate the source for reprints https://funteas.com/post/67d938b4a941bf71340c64f4