使用片段
一个 GraphQL 片段 是一个共享的查询逻辑片段。
fragment NameParts on Person {
firstName
lastName
}
query GetPerson {
people(id: "7") {
...NameParts
avatar(size: LARGE)
}
}
需要注意的是,on
子句后的组件用于我们从中选择的类型。在本例中,people
的类型为 Person
,我们想要从 people(id: "7")
中选择 firstName
和 lastName
字段。
在 Apollo 中,片段主要有两种用途
- 在多个查询、变异或订阅之间共享字段。
- 将查询分解,以便您可以将字段访问与使用它们的位置放在一起。
在本文件中,我们将概述执行这两种操作的模式;我们还将使用 graphql-anywhere
和 graphql-tag
包中的实用程序,这些实用程序旨在帮助我们,尤其是在解决第二个问题时。
重用片段
片段最直接的用途是在应用程序的不同部分重用查询(或变异或订阅)的一部分。例如,在 GitHunt 的评论页面上,我们希望在发布评论后获取与最初查询相同的字段。这样,我们可以确保在数据更改时呈现一致的评论对象。
为此,我们可以简单地共享一个描述我们需要的评论字段的片段
import gql from 'graphql-tag'
export const commentFragment = {
comment: gql`
fragment CommentsPageComment on Comment {
id
postedBy {
login
htmlUrl
}
createdAt
content
}
`,
}
这可以在我们的组件 <script>
部分或按照惯例在同级 fragments.js
文件中完成。
当需要将片段嵌入查询时,我们只需在 GraphQL 文档中使用 ...Name
语法,并将片段嵌入到我们的查询 GraphQL 文档中
const SUBMIT_COMMENT_MUTATION = gql`
mutation SubmitComment($repoFullName: String!, $commentContent: String!) {
submitComment(repoFullName: $repoFullName, commentContent: $commentContent) {
...CommentsPageComment
}
}
${commentFragment}
`
export const COMMENT_QUERY = gql`
query Comment($repoName: String!) {
# ...
entry(repoFullName: $repoName) {
# ...
comments {
...CommentsPageComment
}
# ...
}
}
${commentFragment}
`
您可以在 GitHunt 中查看 CommentsPage
的完整源代码 这里。
将片段放在一起
GraphQL 的一个关键优势是响应数据的树状结构,在许多情况下,它反映了您渲染的组件层次结构。这与 GraphQL 对片段的支持相结合,使您可以将查询拆分为多个部分,以便查询获取的各种字段与使用该字段的代码位于同一位置。
虽然这种技术并不总是有效(例如,GraphQL 架构并不总是由 UI 需求驱动的),但当它有效时,可以在 Apollo 客户端中使用一些模式来充分利用它。
在 GitHunt 中,我们在 FeedPage
上展示了这方面的示例,它构建了以下视图层次结构
FeedPage
└── Feed
└── FeedEntry
├── RepoInfo
└── VoteButtons
FeedPage
执行查询以获取 Entry
列表,每个子组件都需要每个 Entry
的不同子字段。
graphql-anywhere
包为我们提供了工具,可以轻松地构建一个提供每个子组件所需的所有字段的单个查询,并允许轻松地将子组件所需的精确字段传递给它。
创建片段
要创建片段,我们再次使用 gql
帮助程序,例如
// VueButtons.vue
export const entryFragment = gql`
fragment VoteButtons on Entry {
score
vote {
voteValue
}
}
`
如果我们的片段包含子片段,那么我们可以将它们传递给 gql
帮助程序
import { entryFragment as VoteButtonsEntryFragment } from './VoteButtons.vue'
import { entryFragment as RepoInfoEntryFragment } from './RepoInfo.vue'
export const entryFragment = gql`
fragment FeedEntry on Entry {
commentCount
repository {
fullName
htmlUrl
owner {
avatarUrl
}
}
...VoteButtons
...RepoInfo
}
${VoteButtonsEntryFragment}
${RepoInfoEntryFragment}
`
使用片段进行过滤
我们还可以使用 graphql-anywhere
包在将字段传递给子组件之前从 entry
中过滤出精确的字段。因此,当我们渲染 VoteButtons.vue
时,我们可以简单地执行以下操作
<script>
import { filter } from 'graphql-anywhere'
import { entryFragment as VoteButtonsEntryFragment } from './VoteButtons.vue'
setup () {
return {
entry: ...,
filterVoteButtonEntry: entry => filter(VoteButtonsEntryFragment, entry),
}
}
</script>
<template>
<VoteButtons
:entry="filterVoteButtonEntry(entry)"
/>
</template>
filter()
函数将从 entry
中获取片段定义的精确字段。
在使用 Webpack 时导入片段
使用 graphql-tag/loader 加载 .graphql
文件时,我们可以使用 import
语句包含片段。例如
#import "./someFragment.graphql"
query getSomething {
something {
...SomethingFragment
}
}
将使 someFragment.graphql
的内容可用于当前文件。
联合和接口上的片段
默认情况下,Apollo Client 不需要任何关于 GraphQL 架构的知识,这意味着它非常容易设置,可以与任何服务器一起使用,并且支持即使是最庞大的架构。但是,随着您对 Apollo 和 GraphQL 的使用变得更加复杂,您可能会开始在接口或联合上使用片段。以下是一个在接口上使用片段的查询示例
query {
allPeople {
... on Character {
name
}
... on Jedi {
side
}
... on Droid {
model
}
}
}
在上面的查询中,allPeople
返回类型为 Character[]
的结果。Jedi
和 Droid
都是 Character
的可能具体类型,但在客户端,如果没有关于架构的信息,就无法知道这一点。默认情况下,Apollo Client 的缓存将使用启发式片段匹配器,它假设如果结果包含其选择集中所有字段,则片段匹配,如果缺少任何字段,则不匹配。这在大多数情况下有效,但也意味着 Apollo Client 无法为您检查服务器响应,也无法在您使用 update
、updateQuery
、writeQuery
等手动将无效数据写入存储时告诉您。要将这些多态关系告知缓存存储,您需要将 possibleTypes
选项传递给下面的 InMemoryCache
。
以下部分说明如何将必要的架构知识传递给 Apollo Client 缓存,以便可以准确地匹配联合和接口,并在将结果写入存储之前对其进行验证。
我们建议设置一个构建步骤,将必要的架构信息提取到一个 JSON 文件中,以便在构建缓存时可以从中导入该文件。要设置它,请按照以下步骤操作
- 查询您的服务器/架构以获取有关联合和接口的必要信息,并将其写入文件。
阅读有关如何 使用内省查询自动提取 possibleTypes 的文档。或者使用插件 fragment-matcher 用于 graphql-codegen 并将其配置为 apollo client 3。
- 使用
possibleTypes.json
在构建期间配置您的缓存。然后,将新配置的缓存传递给ApolloClient
以完成该过程。
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client/core'
import possibleTypes from './possibleTypes.json'
const cache = new InMemoryCache({ possibleTypes })
const httpLink = createHttpLink({ uri });
const client = new ApolloClient({
cache,
link: httpLink,
})