GraphQL 片段 是一个代码间共享的查询逻辑片段。

1
2
3
4
5
6
7
8
9
10
11
fragment NameParts on Person {
firstName
lastName
}
query getPerson {
people(id: "7") {
...NameParts
avatar(size: LARGE)
}
}

Apollo 的片段主要有两种用途:

  • 在多个查询,突变或订阅之间共享字段。
  • 切分单个查询,让你可以将组件代码和其所需的字段相邻放置。

在本文中,我们将分别概述这两者的模式;我们还将使用 graphql-anywheregraphql-tag 中的公共方法来提供帮助,特别是在第二种情况下。

复用片段

片段的最直接的使用场景是在应用的各个部分重用部分查询(或突变,或订阅)。例如,在 GitHunt 项目中,对于评论页,我们希望在发布评论之后,能够获取与最初的查询相同的字段。这样,我们可以确保在数据更改时渲染一致的评论对象。

为此,我们可以简单地共享一个描述我们所需评论字段的片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { gql } from 'react-apollo';
CommentsPage.fragments = {
comment: gql`
fragment CommentsPageComment on Comment {
id
postedBy {
login
html_url
}
createdAt
content
}
`,
};

按照惯例,我们把这个片段放在 ​​CommentsPage.fragments.comment 里,使用熟悉的 gql 辅助函数来创建它。

当需要将片段嵌入到查询中时,我们只需在 GraphQL 中使用 ...Name 语法,并将该片段嵌入到查询 GraphQL 文本中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const SUBMIT_COMMENT_MUTATION = gql`
mutation submitComment($repoFullName: String!, $commentContent: String!) {
submitComment(repoFullName: $repoFullName, commentContent: $commentContent) {
...CommentsPageComment
}
}
${CommentsPage.fragments.comment}
`;
export const COMMENT_QUERY = gql`
query Comment($repoName: String!) {
# ...
entry(repoFullName: $repoName) {
# ...
comments {
...CommentsPageComment
}
# ...
}
}
${CommentsPage.fragments.comment}
`;

你可以在这里查看 GitHunt 中 CommentsPage 的完整源代码。

协调片段

GraphQL 还有一个主要优点是响应数据的树状特征,在许多情况下,这些属性反映了你渲染的组件的层次结构。这与 GraphQL 对片段的支持相结合,可以让你将查询切分开,使得查询获取的各个字段位于使用该字段的代码旁边。

虽然这种技术并不总是有意义的(例如,GraphQL 的 schema 并不总是由 UI 的需求而驱动设计),但是如果这样做,便可以使用 Apollo 客户端中的一些模式来充分利用它。

在 GitHunt 中,我们在 FeedPage 上展示了一个例子,它构建了如下的视图结构:

1
2
3
4
5
FeedPage
└── Feed
└── FeedEntry
├── RepoInfo
└── VoteButtons

FeedPage 执行查询以获取 Entry 列表,每个子组件所需的 Entry 又有不同的子字段。

graphql-anywhere 包为我们提供了一个工具来轻松构建一个单一查询,该查询提供了每个子组件需要的所有字段,并允许轻易地为组件传递所需的确切字段。

创建片段

要创建片段,我们需要再次使用 gql 辅助函数并将其赋值给 ComponentClass.fragment 的子字段,例如:

1
2
3
4
5
6
7
8
9
10
VoteButtons.fragments = {
entry: gql`
fragment VoteButtons on Entry {
score
vote {
vote_value
}
}
`,
};

graphql-anywhere 包还给我们提供了一个很棒的工具,它是一个 PropType 检查器,我们可以使用它来确保我们确实能在组件的 entry prop 中接收到这些字段:

1
2
3
4
5
6
import { propType } from 'graphql-anywhere';
VoteButtons.propTypes = {
// ...
entry: propType(VoteButtons.fragments.entry).isRequired,
};

如果我们的片段包含子片段,我们同样可以将它们传递给 gql 辅助函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FeedEntry.fragments = {
entry: gql`
fragment FeedEntry on Entry {
commentCount
repository {
full_name
html_url
owner {
avatar_url
}
}
...VoteButtons
...RepoInfo
}
${VoteButtons.fragments.entry}
${RepoInfo.fragments.entry}
`,
};

使用片段进行过滤

我们还可以使用 graphql-anywhere 包从 entry 中过滤出确切的字段,然后再将它们传递给子组件。所以当我们渲染一个 VoteButtons 时,我们可以简单地写:

1
2
3
4
5
6
7
8
9
10
import { filter } from 'graphql-anywhere';
<VoteButtons
entry={filter(VoteButtons.fragments.entry, entry)}
canVote={loggedIn}
onVote={type => onVote({
repoFullName: full_name,
type,
})}
/>

filter() 函数将从片段定义的 entry 中获取完整的字段。

使用 Webpack 导入片段

当使用 graphql-tag/loader 加载 .graphql 文件时,我们可以使用 import 语句引入片段。例如:

1
#import "./someFragment.graphql"

这将使 someFragment.graphql 的内容应用于当前文件。有关更多详细信息,请参阅 Webpack Fragments 部分。

Edit on GitHub