从10月8日-10日加入我们,在纽约市了解关于 GraphQL 联邦和 API 平台工程的最新技巧、趋势和新闻。加入我们参加2024年纽约市的 GraphQL 峰会
文档
免费开始

片段

在操作之间共享字段


AGraphQL 片段是可以在多个查询和之间共享的逻辑片段。

这是一个可以与任何NameParts 使用的声明示例:Person 对象:

fragment NameParts on Person {
firstName
lastName
}

每一个片段都包含属于其关联类型的子集字段。在上面的示例中,Person类型必须声明firstNamelastName字段,以便NameParts片段有效。

现在,我们可以将NameParts片段包含在任何数量的查询和突变中中,这些操作都涉及Person对象,如下所示:

query GetPerson {
people(id: "7") {
...NameParts
avatar(size: LARGE)
}
}

你可以使用三个点(...)前置一个包含的片段,就像JavaScript的展开语法

根据我们的NameParts定义,上述等同于:

query GetPerson {
people(id: "7") {
firstName
lastName
avatar(size: LARGE)
}
}

如果我们以后更改包含在NameParts片段中的字段,我们将自动更改使用该片段的操作包含的字段。这减少了在一系列操作中保持字段一致所需的努力。

示例使用

假设我们有一个博客应用程序,它执行多个与评论相关的操作(提交评论、获取文章评论等)。这些操作可能都包含类型的某些字段。

为了指定这个核心字段集,我们可以在Comment类型上定义一个片段,例如:

fragments.js
import { gql } from '@apollo/client';
export const CORE_COMMENT_FIELDS = gql`
fragment CoreCommentFields on Comment {
id
postedBy {
username
displayName
}
createdAt
content
}
`;

您可以在应用程序的任何文件中声明。上面的示例export片段从fragments.js文件导出。

然后我们可以将CoreCommentFields 片段在以下中使用:

PostDetails.jsx
import { gql } from '@apollo/client';
import { CORE_COMMENT_FIELDS } from './fragments';
export const GET_POST_DETAILS = gql`
${CORE_COMMENT_FIELDS}
query CommentsForPost($postId: ID!) {
post(postId: $postId) {
title
body
author
comments {
...CoreCommentFields
}
}
}
`;
// ...PostDetails component definition...
  • 我们首先import CORE_COMMENT_FIELDS 因为它们是在另一个文件中声明的。
  • 我们将我们的片段定义添加到GET_POST_DETAILS gql模板字面量中,通过占位符(${CORE_COMMENT_FIELDS}
  • 我们在我们的查询中使用标准的...符号来包含CoreCommentFields片段。

使用 createFragmentRegistry 注册命名的片段

从 Apollo Client 3.7 开始,片段可以与您的InMemoryCache注册,这样它们可以在查询或任何InMemoryCache操作(如cache.readFragmentcache.readQuerycache.watch)中通过名称引用,而无需在声明中插值。

让我们看看React中的示例。

index.js
import { ApolloClient, gql, InMemoryCache } from "@apollo/client";
import { createFragmentRegistry } from "@apollo/client/cache";
const client = new ApolloClient({
uri: "https://127.0.0.1:4000/graphql",
cache: new InMemoryCache({
fragments: createFragmentRegistry(gql`
fragment ItemFragment on Item {
id
text
}
`)
})
});

由于ItemFragment已经在InMemoryCache中注册,所以它可以用名称引用,如下面片段GetItemList查询中展开所示。

ItemList.jsx
const listQuery = gql`
query GetItemList {
list {
...ItemFragment
}
}
`;
function ToDoList() {
const { data } = useQuery(listQuery);
return (
<ol>
{data?.list.map(item => (
<Item key={item.id} text={item.text} />
))}
</ol>
);
}

使用本地版本覆盖已注册的片段

查询可以声明它们自己的本地版本命名的片段,这将优先级高于通过createFragmentRegistry注册的片段,即使本地片段只是由其他已注册片段间接引用。以下是一个例子:

index.js
import { ApolloClient, gql, InMemoryCache } from "@apollo/client";
import { createFragmentRegistry } from "@apollo/client/cache";
const client = new ApolloClient({
uri: "https://127.0.0.1:4000/graphql",
cache: new InMemoryCache({
fragments: createFragmentRegistry(gql`
fragment ItemFragment on Item {
id
text
...ExtraFields
}
fragment ExtraFields on Item {
isCompleted
}
`)
})
});

ItemList.jsx中声明的ExtraFields片段的本地版本优先于最初与InMemoryCache一起注册的ExtraFields。因此,当在注册的ItemFragment中解析扩展的ExtraFields片段时,只会使用其定义(仅在执行GetItemList查询时),因为显式定义优先于已注册片段。

ItemList.jsx
const listQuery = gql`
query GetItemList {
list {
...ItemFragment
}
}
fragment ExtraFields on Item {
createdBy
}
`;
function ToDoList() {
const { data } = useQuery(listQuery);
return (
<ol>
{data?.list.map((item) => (
{/* `createdBy` exists on the returned items, `isCompleted` does not */}
<Item key={item.id} text={item.text} author={item.createdBy} />
))}
</ol>
);
}

并列放置片段

GraphQL响应的树形结构类似于前端渲染组件的层次结构。由于这种相似性,您可以使用片段片段查询逻辑拆分在组件之间,以便每个组件正好请求它使用的字段。这有助于您使组件逻辑更加简洁。

考虑以下应用程序的可视层次结构

FeedPage
└── Feed
└── FeedEntry
├── EntryInfo
└── VoteButtons

在这个应用程序中,FeedPage组件执行一个查询来获取FeedEntry对象的列表。子组件EntryInfoVoteButtons需要从包围的FeedEntry对象中获取特定的字段。

创建联动物件片段

携手合作片段就像任何其他片段一样,只是它附加到使用片段字段的特定组件。例如,VoteButtonsFeedPage的子组件可能会使用来自FeedEntry对象的scorevote { choice }字段:

VoteButtons.jsx
VoteButtons.fragments = {
entry: gql`
fragment VoteButtonsFragment on FeedEntry {
score
vote {
choice
}
}
`,
};

在子组件中定义片段之后,父组件可以在其自己的联同伴分中引用它,如下所示:

FeedEntry.jsx
FeedEntry.fragments = {
entry: gql`
fragment FeedEntryFragment on FeedEntry {
commentCount
repository {
full_name
html_url
owner {
avatar_url
}
}
...VoteButtonsFragment
...EntryInfoFragment
}
${VoteButtons.fragments.entry}
${EntryInfo.fragments.entry}
`,
};

关于VoteButtons.fragments.entryEntryInfo.fragments.entry的命名没有什么特别之处。任何命名约定都行,只要您能根据组件检索到组件的片段。

使用Webpack时导入片段

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

#import "./someFragment.graphql"

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

使用联合和接口上的片段

您可以在联合和接口上定义片段。

以下是一个包含三个内联片段的查询示例:

query AllCharacters {
all_characters {
... on Character {
name
}
... on Jedi {
side
}
... on Droid {
model
}
}
}

上面的 all_characters 查询返回一个 Character 对象列表。 Character 类型是一个接口,JediDroid 类型都实现了该接口。列表中的每个项如果是一个 Jedi 类型的对象,则包含一个 字段;如果是一个 Droid 类型的对象,则包含一个 model 字段。

但是,要使此查询能够工作,您的客户端需要理解 Character 接口及其实现类型之间的多态关系。为了通知客户端这些关系,您可以在初始化您的 InMemoryCache 时传递一个 possibleTypes 选项。

手动定义 possibleTypes

possibleTypes 选项在 Apollo Client 3.0 及更高版本中可用。

您可以将 possibleTypes 选项传递给 InMemoryCache 构造函数以指定架构中的超类与子类之间的关系。此对象将一个接口或联合类型的名称(超类)映射到实现或属于它的类型(子类)。

以下是一个 possibleTypes 声明的示例:

const cache = new InMemoryCache({
possibleTypes: {
Character: ["Jedi", "Droid"],
Test: ["PassingTest", "FailingTest", "SkippedTest"],
Snake: ["Viper", "Python"],
},
});

该示例列出了三个接口(CharacterTestSnake)以及实现它们的对象类型。

如果您的架构只包含少数几个联合和接口,您可能可以手动指定 possibleTypes 而无问题。但是,当您的架构在规模和复杂性方面增长时,您应考虑 自动从架构生成 possibleTypes

自动生成 possibleTypes

以下示例脚本将GraphQL 查询转换为一个 possibleTypes 配置对象:

const fetch = require('cross-fetch');
const fs = require('fs');
fetch(`${YOUR_API_HOST}/graphql`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
variables: {},
query: `
{
__schema {
types {
kind
name
possibleTypes {
name
}
}
}
}
`,
}),
}).then(result => result.json())
.then(result => {
const possibleTypes = {};
result.data.__schema.types.forEach(supertype => {
if (supertype.possibleTypes) {
possibleTypes[supertype.name] =
supertype.possibleTypes.map(subtype => subtype.name);
}
});
fs.writeFile('./possibleTypes.json', JSON.stringify(possibleTypes), err => {
if (err) {
console.error('Error writing possibleTypes.json', err);
} else {
console.log('Fragment types successfully extracted!');
}
});
});

然后您可以将生成的possibleTypes JSON模块导入到创建您的InMemoryCache的地方:

import possibleTypes from './path/to/possibleTypes.json';
const cache = new InMemoryCache({
possibleTypes,
});

useFragment

The useFragment hook将轻量级的实时绑定到 Apollo 客户端的缓存。它允许 Apollo 客户端向单个组件广播特定的fragment结果。此 hook 返回缓存当前为给定fragment包含的任何数据的始终最新的视图。useFragment永远不会触发它自己的网络请求。

useQuery hook 仍然负责在缓存中查询和填充数据的 主要 hook查看 API 参考)。因此,通过 fragment数据通过useFragment读取的组件仍然订阅query数据中的所有变更,但只有在特定 fragment的数据变更时才会收到更新。

注意:此 hook 在3.7.0作为实验性功能引入,但在3.8.0中稳定下来。在3.7.x3.8.0-alpha.x版本中,此 hook 以useFragment_experimental的形式导出。从3.8.0-beta.0及更高版本开始,其命名导出中不再包含_experimental后缀。

示例

考虑到以下fragment定义:

const ItemFragment = gql`
fragment ItemFragment on Item {
text
}
`;

我们可以首先使用useQuery hook 获取具有id的项以及在任何命名的ItemFragment fragment 上选择的任何字段。通过在ListQuery中的list内部散播ItemFragment done,可以完成此操作。

const listQuery = gql`
query GetItemList {
list {
id
...ItemFragment
}
}
${ItemFragment}
`;
function List() {
const { loading, data } = useQuery(listQuery);
return (
<ol>
{data?.list.map(item => (
<Item key={item.id} id={item.id}/>
))}
</ol>
);
}

注意:我们可以在每次query中内插fragments的界面,我们还可以使用 Apollo 客户端的createFragmentRegistry方法预先将命名的fragments注册到我们的InMemoryCache中。这允许 Apollo 客户端在网络请求之前将注册的fragment的定义包括在要发送的文档中。有关更多信息,请参阅使用createFragmentRegistry注册命名的fragments

然后,我们可以在<Item>组件内部使用useFragment,通过提供fragment documentfragmentName及通过 from的对象引用,为每个项创建实时绑定。

function Item(props: { id: number }) {
const { complete, data } = useFragment({
fragment: ItemFragment,
fragmentName: "ItemFragment",
from: {
__typename: "Item",
id: props.id,
},
});
return <li>{complete ? data.text : "incomplete"}</li>;
}

useFragment 可以与 @nonreactive 指令结合使用,在需要列表项对单个缓存更新进行响应而不重新渲染整个列表的情况下。有关更多信息,请参阅 @nonreactive 文档

请参阅API参考获取更多详细信息。

上一页
订阅
下一页
指令
给这篇文章评分评分在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,doing business as Apollo GraphQL.

隐私政策

公司