Apollo Link 概述
定制 Apollo 客户端的数据流
如果你的应用程序只需要向一个GraphQL 服务器发送常规基于HTTP的请求,你可能不需要使用Apollo Link API。请参见基本HTTP网络。
。Apollo Link库帮助您自定义 Apollo 客户端与您的 GraphQL 服务器之间的数据流。您可以定义客户端的网络行为为一个链式的link对象,按顺序执行:
每个链应该代表对 GraphQL 操作的自我包含修改或者副作用(例如日志记录)。
在上面的图中
- 第一个链可能为了调试目的记录操作详情。
- 第二个链可能为了认证目的向出站的操作请求中添加HTTP头部。
- 最后的(终止的)链将操作发送到目的地(通常是HTTP的 GraphQL 服务器)。
- 服务器的响应将按逆序传回每个链,这样链就可以修改响应或在实际数据被缓存前采取其他行动。
默认情况下,Apollo 客户端使用 Apollo Link 的
HttpLink
用于发送GraphQL操作到远程服务器通过HTTP。Apollo Client负责创建此默认链,并且无需额外自定义即可覆盖许多用例。
要扩展或替换这种默认的网络行为,您可以在ApolloClient
构造函数中定义自定义链并指定它们的执行顺序。
您的第一个链接链
以下示例展示了包含两个Apollo提供的链接的基本链接链
- 一个
onError
链接,它会检查服务器响应中的错误。它会记录找到的任何错误详情。 - 一个
HttpLink
将每个GraphQL操作发送到您的服务器。- 这是链的终结链接。
请注意,如果您向ApolloClient
构造函数提供链接链,则不要提供uri
选项。相反,您需要将服务器的URL提供到您的HttpLink
中。
创建自定义链接
链接对象是ApolloLink
类(或其子类)的实例。每个链接都必须定义一个名为request
的方法,这被称为链接的请求处理程序。您可以将此方法的定义提供给ApolloLink
构造函数。
示例
以下自定义链接定义了一个请求处理器,该处理器将 GraphQL 操作的近似开始时间添加到操作的 上下文:
import { ApolloLink } from '@apollo/client';const timeStartLink = new ApolloLink((operation, forward) => {operation.setContext({ start: new Date() });return forward(operation);});
然后此请求处理器调用 return forward(operation)
,这是调用链中下一个链接语法的句法。
请求处理器
每个链接必须定义一个 request
方法,也称为它的 请求处理器。 此方法传入以下 参数:
operation
:正在通过链接传递的 GraphQL 操作。- 详细信息请参阅 关于
Operation
对象。
- 详细信息请参阅 关于
forward
:一个用于执行链中下一个链接的函数(除非这是一个 终止链接)。
每当 Apollo Client 准备执行 GraphQL 操作时,它都会调用链中第一个链接的请求处理器。每个链接都负责执行其逻辑,然后通过调用 forward
函数并返回其结果来将执行传递给下一个链接。
Operation
对象
The Operation
对象包括以下 字段:
名称 | 说明 |
---|---|
查询 | 一个 DocumentNode (解析的 GraphQL 操作),描述正在进行的操作。 |
变量 | 一个映射,携带与操作一起发送的 GraphQL 变量。 |
operationName | 如果有命名,则为查询的字符串名称,否则 null 。 |
扩展 | 一个映射,用于存储要发送到服务器的扩展数据。 |
getContext | 一个用于返回请求上下文的函数。此上下文可以由链接用于确定要执行哪些操作。请参阅 管理上下文。 |
setContext | 一个函数,它接受一个新的上下文对象,或者一个函数,该函数接受先前的上下文并返回一个新对象。请参阅 管理上下文。 |
forward
函数
当您的自定义链接的请求处理器执行完其逻辑后,它应该返回一个对传入的 forward
函数的调用(除非它是链的 终止链接)。调用 return forward(operation)
会将执行传递给链中的下一个链接。
如果一个非终止的自定义链接的请求处理器 不 return forward(operation)
,则链接链终止,并且相关的 GraphQL 操作不会执行。
前进函数的返回类型是由 zen-observable
库提供的一个 Observable
类型。有关 zen-observable
的详细信息,请参阅其文档。
处理响应
当您的 GraphQL 服务器对操作返回结果时,该结果会在缓存之前,通过链中的每个链接传递上去:
每个链接可以通过修改其对请求处理者的 return
语句来执行逻辑,如下所示:
// BEFORE (NO INTERACTION)return forward(operation);// AFTERreturn forward(operation).map((data) => {// ...modify result as desired here...return data;});
传给 map
的函数返回的任何内容都会传递到链中的下一个链接。
在这里也可以执行与结果无关的逻辑。此请求处理程序使用 请求上下文 来估计每个操作往返延时:
import { ApolloLink } from '@apollo/client';const roundTripLink = new ApolloLink((operation, forward) => {// Called before operation is sent to serveroperation.setContext({ start: new Date() });return forward(operation).map((data) => {// Called after server respondsconst time = new Date() - operation.getContext().start;console.log(`Operation ${operation.operationName} took ${time} to complete`);return data;});});
组合链接链
每个链接代表对 GraphQL 操作的独立修改或副作用(如日志记录)。通过将这些链接组合成链,您可以创建任意复杂的数据流模型。
链接有两种组合形式:累加和方向性。
请注意,无论链如何分支,每个分支最终都会以一个 终止链接 结尾。
终止链接
终止链接是链接链中的最后一个链接。它不调用 终止链接 中的 forward
函数,而是负责将组合的 GraphQL 操作发送到你执行它的目的地(通常是 GraphQL 服务器),并返回一个 ExecutionResult
。
HttpLink
和 BatchHttpLink
都是终止链接的示例。
累加组合
如果您有一组两个或更多应按顺序执行的链接,请使用 ApolloLink.from
辅助方法将那些链接组合成一个 单个 链接,如下所示:
import { from, HttpLink } from '@apollo/client';import { RetryLink } from '@apollo/client/link/retry';import MyAuthLink from '../auth';const additiveLink = from([new RetryLink(),new MyAuthLink(),new HttpLink({ uri: 'https://127.0.0.1:4000/graphql' })]);
方向性组合
您可能希望链的执行根据操作的详细信息进行分支。
为了支持这一点,@apollo/client
库提供了一个 split
函数,允许您根据布尔检查的结果使用两个不同的 Link
。您还可以使用 ApolloLink
实例的 split
方法。
名称 | 说明 |
---|---|
测试 | 一个函数,接收当前的 Operation ,并根据其详细信息返回 true 或 false 。 |
左 | 如果测试函数返回 true ,则执行下一个链接。 |
右 | 一个 可选 的链接,如果测试函数返回 false ,则执行下一个链接。如果没有提供,则使用请求处理器的 forward 参数。 |
在以下示例中,一个 RetryLink
会根据相关上下文的 version
将执行传递给两个不同的 HttpLink
:
import { ApolloLink, HttpLink } from '@apollo/client';import { RetryLink } from '@apollo/client/link/retry';const directionalLink = new RetryLink().split((operation) => operation.getContext().version === 1,new HttpLink({ uri: 'https://127.0.0.1:4000/v1/graphql' }),new HttpLink({ uri: 'https://127.0.0.1:4000/v2/graphql' }));
split
方法的其他用途包括:
- 根据操作类型自定义允许的重试尝试次数
- 根据操作类型使用不同的传输方法(例如,查询使用 HTTP 而订阅使用 WebSocket)
- 根据用户是否登录自定义逻辑
在以下示例中,所有 订阅 操作都发送到 GraphQLWsLink
,所有其他操作都发送到 HttpLink
:
import { split, HttpLink } from '@apollo/client';import { getMainDefinition } from '@apollo/client/utilities';import { GraphQLWsLink } from '@apollo/client/link/subscriptions';import { createClient } from 'graphql-ws';export const link = split(({ query }) => {const definition = getMainDefinition(query);return (definition.kind === 'OperationDefinition' &&definition.operation === 'subscription');},new GraphQLWsLink(createClient({ url: 'ws://127.0.0.1:3000/subscriptions' })),new HttpLink({ uri: 'https://127.0.0.1:4000/graphql' }));
提供 Apollo Client
在您完成整个链接链的构建之后,将结果链接提供给 ApolloClient
构造函数,如下所示:
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';import { RetryLink } from '@apollo/client/link/retry';const directionalLink = new RetryLink().split((operation) => operation.getContext().version === 1,new HttpLink({ uri: "https://127.0.0.1:4000/v1/graphql" }),new HttpLink({ uri: "https://127.0.0.1:4000/v2/graphql" }));const client = new ApolloClient({cache: new InMemoryCache(),link: directionalLink});
链接类型
无状态链接
大多数链接为每个 operation 执行相同的逻辑,并且它们不需要了解之前执行的 operation。这些链接是 无状态的。
您可以在 ApolloLink
对象的构造函数中定义无状态链接的请求处理器,如下所示:
import { ApolloLink } from '@apollo/client';const consoleLink = new ApolloLink((operation, forward) => {console.log(`starting request for ${operation.operationName}`);return forward(operation).map((data) => {console.log(`ending request for ${operation.operationName}`);return data;})})
无状态链接非常适合实现中间件甚至网络请求。以下链接为每个出去的请求添加了一个 Authorization
头:
import { ApolloLink } from '@apollo/client';const authLink = new ApolloLink((operation, forward) => {operation.setContext(({ headers }) => ({ headers: {authorization: Auth.userId(), // however you get your token...headers}}));return forward(operation);});
这种风格的链接也适用于函数的定制
import { ApolloLink } from '@apollo/client';const reportErrors = (errorCallback) => new ApolloLink((operation, forward) => {return new Observable((observer) => {const observable = forward(operation);const subscription = observable.subscribe({next(value) {observer.next(value);},error(networkError) {errorCallback({ networkError, operation });observer.error(networkError);},complete() {observer.complete();},});return () => subscription.unsubscribe();});});const link = reportErrors(console.error);
扩展 ApolloLink
您还可以通过扩展 ApolloLink
类和覆盖其构造函数和请求处理器来创建无状态链接。例如,以下是将相同的 reportErrors
链接编写为 ApolloLink
扩展的示例:
import { ApolloLink } from '@apollo/client';class ReportErrorLink extends ApolloLink {constructor(errorCallback) {super();this.errorCallback = errorCallback;}request(operation, forward) {const observable = forward(operation);// errors will be sent to the errorCallbackobservable.subscribe({ error: this.errorCallback })return observable;}}const link = new ReportErrorLink(console.error);
有状态链接
有用时,链接可以在 operation 之间维护状态。这些链接是 有状态的。
有状态的链接通常被定义为 ApolloLink
的子类。它们覆盖了 ApolloLink
的构造函数,并实现了一个具有与无状态链接相同签名的 request
函数。例如:
import { ApolloLink } from '@apollo/client';class OperationCountLink extends ApolloLink {constructor() {super();this.operationCount = 0;}request(operation, forward) {this.operationCount += 1;return forward(operation);}}const link = new OperationCountLink();
这个有状态的链接维护一个名为 operationCount
的计数器作为实例变量。每当请求穿过链接时,operationCount
就会增加。
管理上下文
作为一个操作沿着你的链接链移动时,它会维护一个 context
,每个链接都可以读取和修改。这使得链接可以在链中传递元数据,其他链接可以在它们的执行逻辑中使用这些元数据。
- 通过调用
operation.getContext()
来获取当前上下文对象。 - 修改上下文对象,然后通过
operation.setContext(newContext)
或operation.setContext((prevContext) => newContext)
将其写回。
请注意,这个上下文不会被包含在终止链接发给 GraphQL 服务器或其他目的地的请求中。
以下是一个示例
import { ApolloLink, from } from '@apollo/client';const timeStartLink = new ApolloLink((operation, forward) => {operation.setContext({ start: new Date() });return forward(operation);});const logTimeLink = new ApolloLink((operation, forward) => {return forward(operation).map((data) => {// data from a previous linkconst time = new Date() - operation.getContext().start;console.log(`operation ${operation.operationName} took ${time} to complete`);return data;})});const additiveLink = from([timeStartLink,logTimeLink]);
这个示例定义了两个链接,timeStartLink
和 logTimeLink
。链接 timeStartLink
将当前时间赋值到上下文的 start
字段。操作完成时,链接 logTimeLink
从当前时间减去 start
的值,以确定操作的总持续时间。
你可以通过向 useQuery
钩子 (或 useMutation
、useSubscription
等)提供 context
参数来设置特定操作的上下文对象的初始值。