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

订阅

从您的 GraphQL 服务器获取实时更新


除了查询变更之外,还支持第三种操作类型:订阅

像查询一样,允许您获取数据。不同之处在于,订阅是长期存在的操作,其结果会随时间变化。它们可以保持与您的的活跃连接(通常是通过 WebSocket),允许服务器向订阅的结果推送更新。

对于实时通知客户端后端数据的更改非常有用,例如新对象的创建或重要的更新。

何时使用订阅

在大多数情况下,您的客户端应不要使用订阅来保持与后端的同步。相反,您应通过查询间歇性地轮询,或者在用户执行相关操作(如点击按钮)时重新执行查询。

您应使用订阅进行以下操作:

  • 对大型对象进行少量增量更改。重复轮询大型对象是非常昂贵的,尤其是当对象的大部分很少更改时。相反,您可以使用获取对象的初始状态,并且您的服务器可以主动推送到单个字段在发生时的更新。

  • 低延迟、实时更新。例如,聊天应用程序的客户端希望在新消息可用时立即接收。

注意订阅不能用于监听本地客户端事件,如订阅缓存中的更改。订阅旨在用于订阅外部数据更改,并存储这些接收到的更改。然后您可以使用的观察模型来监视缓存中的更改,使用client.watchQueryuseQuery来监视。

支持的订阅协议

The GraphQL规范没有定义用于发送订阅请求的特定协议。Apollo Client支持以下协议进行订阅:

您必须使用与您通信的GraphQL端点相同的协议。

WebSocket子协议

第一个支持WebSocket上订阅的JavaScript库被称为subscriptions-transport-ws此库不再积极维护。其继任者是一个名为graphql-ws的库。这这两个库不使用相同的WebSocket子协议,因此您需要使用与您的GraphQL端点相同的子协议。

以下 WebSocket 配置部分使用 graphql-ws。如果你的端点使用 subscriptions-transport-ws,请查看 本节,了解配置差异。

注意: 令人困惑的是,subscriptions-transport-ws 库将其 WebSocket 子协议 称为 graphql-ws,而 graphql-ws 称其子协议为 graphql-transport-ws!在本篇文章中,我们指的是两个 subscriptions-transport-wsgraphql-ws),而非两个子协议。

HTTP

要使用 Apollo Client 连接到支持通过 HTTP 进行多部分订阅的 GraphQL 端点,请确保您使用的是 multipart subscriptions over HTTP的版本为 3.7.11或更高版本。

除了更新您的客户端版本外,无需任何其他配置! Apollo Client 会自动将所需的头信息连同请求一起发送,如果传递了一个订阅 HTTPLink 操作。

与 Relay 或 urql 一起使用

在支持 Relay 或 urql 的应用程序中消费通过 HTTP 进行多部分订阅,Apollo Client 提供网络层适配器,用于解析多部分响应格式。

Relay
import { createFetchMultipartSubscription } from '@apollo/client/utilities/subscriptions/relay';
import { Environment, Network, RecordSource, Store } from 'relay-runtime';
const fetchMultipartSubs = createFetchMultipartSubscription('https://api.example.com');
const network = Network.create(fetchQuery, fetchMultipartSubs);
export const RelayEnvironment = new Environment({
network,
store: new Store(new RecordSource()),
});
import { createFetchMultipartSubscription } from '@apollo/client/utilities/subscriptions/relay';
import { Environment, Network, RecordSource, Store } from 'relay-runtime';
const fetchMultipartSubs = createFetchMultipartSubscription('https://api.example.com');
const network = Network.create(fetchQuery, fetchMultipartSubs);
export const RelayEnvironment = new Environment({
network,
store: new Store(new RecordSource()),
});

urql

import { createFetchMultipartSubscription } from '@apollo/client/utilities/subscriptions/urql';
import { Client, fetchExchange, subscriptionExchange } from '@urql/core';
const url = 'https://api.example.com';
const multipartSubscriptionForwarder = createFetchMultipartSubscription(url);
const client = new Client({
url,
exchanges: [
fetchExchange,
subscriptionExchange({
forwardSubscription: multipartSubscriptionForwarder,
}),
],
});
import { createFetchMultipartSubscription } from '@apollo/client/utilities/subscriptions/urql';
import { Client, fetchExchange, subscriptionExchange } from '@urql/core';
const url = 'https://api.example.com';
const multipartSubscriptionForwarder = createFetchMultipartSubscription(url);
const client = new Client({
url,
exchanges: [
fetchExchange,
subscriptionExchange({
forwardSubscription: multipartSubscriptionForwarder,
}),
],
});

定义订阅

您可以在服务器和客户端定义订阅,就像您定义查询和一样。

服务器端

您可以在您的 中定义可用的 订阅,作为 Subscription 类型的字段。以下 commentAdded 订阅会在添加特定博客文章(通过 postID 指定)的新评论时通知订阅客户端:

type Subscription {
commentAdded(postID: ID!): Comment
}

有关在服务器端实现支持 订阅的更多信息,请参阅 Apollo Server 订阅文档

客户端

在您应用的客户端中,您定义每个 订阅的形状,如下所示:

const COMMENTS_SUBSCRIPTION: TypedDocumentNode<
OnCommentAddedSubscription,
OnCommentAddedSubscriptionVariables
> = gql`
subscription OnCommentAdded($postID: ID!) {
commentAdded(postID: $postID) {
id
content
}
}
`;
const COMMENTS_SUBSCRIPTION = gql`
subscription OnCommentAdded($postID: ID!) {
commentAdded(postID: $postID) {
id
content
}
}
`;

Apollo Client 在执行 OnCommentAdded 订阅时,会与您的 GraphQL 服务器建立连接并监听响应数据。与查询不同,没有期望服务器立即处理并返回响应。相反,仅在您的后端发生特定事件时,您的服务器才会向客户端推送数据。

每当您的 GraphQL 服务器 推送数据到订阅客户端时,这些数据符合执行 订阅的结构,就像查询一样:

{
"data": {
"commentAdded": {
"id": "123",
"content": "What a thoughtful and well written post!"
}
}
}

WebSocket 设置

1. 安装所需的库

Apollo Link 是一个库,可以帮助您自定义 Apollo Client 的网络通信。您可以使用它来定义一个 链链,修改您的 operations 并将它们路由到适当的目的地。

要执行 WebSocket 上的订阅,您可以在链链中添加一个 GraphQLWsLink。此链接需要 graphql-ws 库。如下所示进行安装:

npm install graphql-ws

在初始化 ApolloClient 的相同项目文件中导入并初始化一个 GraphQLWsLink 对象:

index.ts
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://127.0.0.1:4000/subscriptions',
}),
);
index.js
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://127.0.0.1:4000/subscriptions',
}),
);

url 选项的值替换为您 GraphQL 服务器特定订阅的 WebSocket 终端。如果您使用的是 ,请参阅 设置订阅端点

虽然 Apollo Client 可以使用您的 GraphQLWsLink 执行所有操作类型,但在大多数情况下,它应该继续使用 HTTP 执行查询和 mutations。这是因为查询和 mutations 不需要状态或持久连接,在没有 WebSocket 连接的情况下,HTTP 更高效且可扩展。

为此,@apollo/client 库提供了一个 split 函数,它允许您根据布尔检查的结果使用两种不同的 Link 之一。

以下示例在之前的基础上扩展了示例,初始化了 GraphQLWsLinkHttpLink。然后使用 split 函数将这两个 Link 合并到一个 single Link,并按执行的操作类型使用其一或另一个。

index.ts
import { split, HttpLink } from '@apollo/client';
import { getMainDefinition } from '@apollo/client/utilities';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
const httpLink = new HttpLink({
uri: 'https://127.0.0.1:4000/graphql',
});
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://127.0.0.1:4000/subscriptions',
}),
);
// The split function takes three parameters:
//
// * A function that's called for each operation to execute
// * The Link to use for an operation if the function returns a "truthy" value
// * The Link to use for an operation if the function returns a "falsy" value
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
},
wsLink,
httpLink,
);
index.js
import { split, HttpLink } from '@apollo/client';
import { getMainDefinition } from '@apollo/client/utilities';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
const httpLink = new HttpLink({
uri: 'https://127.0.0.1:4000/graphql',
});
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://127.0.0.1:4000/subscriptions',
}),
);
// The split function takes three parameters:
//
// * A function that's called for each operation to execute
// * The Link to use for an operation if the function returns a "truthy" value
// * The Link to use for an operation if the function returns a "falsy" value
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
},
wsLink,
httpLink,
);

使用此逻辑,查询和 mutations 将正常使用 HTTP,而 subscriptions 将使用 WebSocket。

定义完您的链接链后,您可以通过 Apollo Client 的 link 构造函数选项提供:

index.ts
import { ApolloClient, InMemoryCache } from '@apollo/client';
// ...code from the above example goes here...
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache()
});
index.js
import { ApolloClient, InMemoryCache } from '@apollo/client';
// ...code from the above example goes here...
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache()
});

如果您提供 link 选项,它将覆盖 uri 选项(uri 使用提供的 URL 设置一个默认的 HTTP 链)。

5. 通过 WebSocket 进行身份验证(可选)

通常需要在允许客户端接收 subscription 结果之前对其进行身份验证。为此,您可以为 GraphQLWsLink 构造函数提供一个 connectionParams 选项,如下所示:

import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
const wsLink = new GraphQLWsLink(createClient({
url: 'ws://127.0.0.1:4000/subscriptions',
connectionParams: {
authToken: user.authToken,
},
}));
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
const wsLink = new GraphQLWsLink(createClient({
url: 'ws://127.0.0.1:4000/subscriptions',
connectionParams: {
authToken: user.authToken,
},
}));

您的 GraphQLWsLink 将在连接时将 connectionParams 对象传递给您的服务器。您的服务器接收 connectionParams 对象并可以使用它进行身份验证,以及执行任何其他连接相关的任务。

通过多部分 HTTP 的订阅

不需要额外的库或配置。Apollo Client 在使用初始化链或 Apollo Client 实例指定的 uri 时,将需要的标头添加到请求中,当默认终止的 HTTPLinksubscription 操作时。

注意:要在 React Native 应用程序中使用多部分 HTTP 的订阅功能,需要额外的配置。有关更多详细信息,请参阅 React Native 文档

执行订阅

您可以使用 Apollo Client 的 useSubscription 钩子从 React 中执行订阅。与 useQuery 类似,useSubscription 返回一个对象,该对象包含Apollo Client 中的 loadingerrordata 属性,您可以使用这些属性来渲染您的 UI。

以下示例组件使用之前定义的订阅来渲染指定博客文章的最新评论。每当 GraphQL 服务器向客户端推送新的评论时,组件会重新渲染并显示新的评论。

const COMMENTS_SUBSCRIPTION: TypedDocumentNode<
OnCommentAddedSubscription,
OnCommentAddedSubscriptionVariables
> = gql`
subscription OnCommentAdded($postID: ID!) {
commentAdded(postID: $postID) {
id
content
}
}
`;
function LatestComment({ postID }: LatestCommentProps) {
const { data, loading } = useSubscription(
COMMENTS_SUBSCRIPTION,
{ variables: { postID } }
);
return <h4>New comment: {!loading && data.commentAdded.content}</h4>;
}
const COMMENTS_SUBSCRIPTION = gql`
subscription OnCommentAdded($postID: ID!) {
commentAdded(postID: $postID) {
id
content
}
}
`;
function LatestComment({ postID }) {
const { data, loading } = useSubscription(
COMMENTS_SUBSCRIPTION,
{ variables: { postID } }
);
return <h4>New comment: {!loading && data.commentAdded.content}</h4>;
}

订阅查询的更新

当Apollo Client 接收到查询结果时,该结果包含一个 subscribeToMore 函数。您可以使用此函数来执行后续的订阅,以便向原始查询结果推送更新。

与用于处理分页的常见函数 fetchMore 相似,subscribeToMore 具有相似的结构。主要区别在于,fetchMore 执行一个后续 查询,而 subscribeToMore 执行一个 订阅。

以下是一个示例:从一个标准的 查询 开始,该查询获取给定博客文章的所有现有评论:

const COMMENTS_QUERY: TypedDocumentNode<
CommentsForPostQuery,
CommentsForPostQueryVariables
> = gql`
query CommentsForPost($postID: ID!) {
post(postID: $postID) {
comments {
id
content
}
}
}
`;
function CommentsPageWithData({ params }: CommentsPageWithDataProps) {
const result = useQuery(
COMMENTS_QUERY,
{ variables: { postID: params.postID } }
);
return <CommentsPage {...result} />;
}
const COMMENTS_QUERY = gql`
query CommentsForPost($postID: ID!) {
post(postID: $postID) {
comments {
id
content
}
}
}
`;
function CommentsPageWithData({ params }) {
const result = useQuery(
COMMENTS_QUERY,
{ variables: { postID: params.postID } }
);
return <CommentsPage {...result} />;
}

假设我们希望我们的 GraphQL 服务器在帖子中添加新评论时立即向客户端推送更新。首先,我们需要定义当 COMMENTS_QUERY 返回时 Apollo Client 将执行的订阅。

const COMMENTS_SUBSCRIPTION: TypedDocumentNode<
OnCommentAddedSubscription,
OnCommentAddedSubscriptionVariables
> = gql`
subscription OnCommentAdded($postID: ID!) {
commentAdded(postID: $postID) {
id
content
}
}
`;
const COMMENTS_SUBSCRIPTION = gql`
subscription OnCommentAdded($postID: ID!) {
commentAdded(postID: $postID) {
id
content
}
}
`;

接下来,我们将 CommentsPageWithData 函数修改为向它返回的 CommentsPage 组件添加一个 subscribeToNewComments 属性。该属性是一个函数,负责在组件挂载后调用 subscribeToMore

function CommentsPageWithData({ params }: CommentsPageWithDataProps) {
const { subscribeToMore, ...result } = useQuery(
COMMENTS_QUERY,
{ variables: { postID: params.postID } }
);
return (
<CommentsPage
{...result}
subscribeToNewComments={() =>
subscribeToMore({
document: COMMENTS_SUBSCRIPTION,
variables: { postID: params.postID },
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
const newFeedItem = subscriptionData.data.commentAdded;
return Object.assign({}, prev, {
post: {
comments: [newFeedItem, ...prev.post.comments]
}
});
}
})
}
/>
);
}
function CommentsPageWithData({ params }) {
const { subscribeToMore, ...result } = useQuery(
COMMENTS_QUERY,
{ variables: { postID: params.postID } }
);
return (
<CommentsPage
{...result}
subscribeToNewComments={() =>
subscribeToMore({
document: COMMENTS_SUBSCRIPTION,
variables: { postID: params.postID },
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
const newFeedItem = subscriptionData.data.commentAdded;
return Object.assign({}, prev, {
post: {
comments: [newFeedItem, ...prev.post.comments]
}
});
}
})
}
/>
);
}

在上面的示例中,我们向 subscribeToMore 传递了三个选项:

  • document 指定了要执行的订阅。
  • variables 指定了执行订阅时包含的变量。
  • updateQuery 是一个函数,它告诉 Apollo Client 如何将查询当前缓存的查询结果(prev)与我们的 GraphQL 服务器推送的订阅数据(subscriptionData)合并。这个函数的返回值会完全替换当前的查询缓存结果。

最后,我们在 CommentsPage 的定义中告诉组件在挂载时调用 subscribeToNewComments

export function CommentsPage({ subscribeToNewComments }: CommentsPageProps) {
useEffect(() => subscribeToNewComments(), []);
return <>...</>
}
export function CommentsPage({ subscribeToNewComments }) {
useEffect(() => subscribeToNewComments(), []);
return <>...</>
}

useSubscription API 参考文档

注意:如果你正在使用 React Apollo 的 Subscription 渲染属性组件,下面列出的选项/结果细节仍然有效(选项是组件属性,结果传递到渲染属性函数中)。唯一不同的是还需要一个 subscription 属性(它包含由 gql 解析成 AST 的 GraphQL 订阅文档)。

选项

useSubscription 钩子接受以下选项:

其他

client(可选)

ApolloClient

ApolloClient 实例。默认情况下,useSubscription / Subscription 使用通过上下文传递下来的客户端,但也可以传递不同的客户端。

DefaultContext

你的组件与你的网络接口(Apollo Link)之间的共享上下文。

指定用于此操作的 ErrorPolicy。

你的组件与你的网络接口(Apollo Link)之间的共享上下文。

你希望你的组件如何与 Apollo 缓存交互。对于详细信息,请参阅 设置获取策略

如果为 true,则钩子将不会使组件重新渲染。当你在 onDataonError 回调中用逻辑控制组件的渲染时,这很有用。

当钩子已经有 data 时将此设置为 true 会使 data 重置为 undefined

onComplete(可选)

Since3.7.0

() => void

允许注册一个回调函数,该函数将在useSubscription钩子/Subscription组件完成订阅时被触发。

(options: OnDataOptions<TData>) => any

允许注册一个回调函数,该函数将在useSubscription钩子/Subscription组件接收数据时被触发。回调函数的options对象参数包括当前的Apollo Client实例client以及接收到的订阅数据data

(error: ApolloError) => void

允许注册一个回调函数,该函数将在useSubscription钩子/Subscription组件接收错误时被触发。

boolean | ((options: BaseSubscriptionOptions<TData, TVariables>) => boolean)

确定当钩子的输入(如subscriptionvariables)更改时,您的订阅是否应取消订阅并重新订阅。

确定当前订阅是否应被跳过。如果有用,例如,当variables依赖于以前的查询且尚未准备好时。

一个对象,包含subscription执行所需的所有variables

⚠️ 已废弃

使用onComplete代替

允许注册一个回调函数,该函数将在useSubscription钩子/Subscription组件完成订阅时被触发。

(options: OnSubscriptionDataOptions<TData>) => any

⚠️ 已废弃

使用onData代替

允许注册一个回调函数,该函数将在useSubscription钩子/Subscription组件接收数据时被触发。回调函数的options对象参数包括当前的Apollo Client实例client以及接收到的订阅数据subscriptionData

结果

调用后,useSubscription钩子返回一个包含以下属性的结果对象:

其他

一个对象,包含您的GraphQL订阅的结果。默认为一个空对象。

一个运行时错误,具有graphQLErrorsnetworkError属性。

boolean

一个布尔值,指示是否已返回任何初始数据

The older subscriptions-transport-ws library

如果你的服务器使用的是 subscriptions-transport-ws 而不是较新的 graphql-ws 库,你需要在设置连接的方式上进行一些修改:

  1. 不要执行 npm install graphql-ws 命令:

    npm install subscriptions-transport-ws
  2. 不要这样 import { createClient } from 'graphql-ws' 导入:

    import { SubscriptionClient } from 'subscriptions-transport-ws'
  3. 不要这样 import { GraphQLWsLink } from '@apollo/client/link/subscriptions' 导入:

    import { WebSocketLink } from '@apollo/client/link/ws'
  4. 传递给 new SubscriptionClient 的参数与传递给 createClient 的参数略有不同:

    • 传递给 构造函数的第一个参数是订阅服务器的 URL。
    • 选项对象 connectionParams 被嵌套在一个名为 options 的选项对象中,而不是位于顶级。你也可以直接将 new SubscriptionClient 的参数传递给 new WebSocketLink。)
    • 请参阅 subscriptions-transport-ws 的 README 文件获取完整的 SubscriptionClient API 文档。

在创建你的 wsLink 后,这篇文章中剩余的内容仍然适用: useSubscription, subscribeToMore 和分叉链接在这两种实现中工作方式完全相同。

以下是一个典型的 WebSocketLink 初始化示例:

import { WebSocketLink } from '@apollo/client/link/ws';
import { SubscriptionClient } from 'subscriptions-transport-ws';
const wsLink = new WebSocketLink(
new SubscriptionClient('ws://127.0.0.1:4000/subscriptions', {
connectionParams: {
authToken: user.authToken,
},
}),
);
import { WebSocketLink } from '@apollo/client/link/ws';
import { SubscriptionClient } from 'subscriptions-transport-ws';
const wsLink = new WebSocketLink(
new SubscriptionClient('ws://127.0.0.1:4000/subscriptions', {
connectionParams: {
authToken: user.authToken,
},
}),
);

有关 WebSocketLink 的更多详细信息,请参阅 其 API 文档

上一页
重新获取
下一页
片段
评分文章评分在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,商业名称Apollo GraphQL。

隐私政策

公司