Apollo Client SchemaLink 全解析:在本地 GraphQL Schema 上执行查询,实现 SSR 与数据 Mocking
2026/9/20 20:55:55 网站建设 项目流程
  • 前端
  • GraphQL

【免费下载链接】apollo-client

The industry-leading GraphQL client for TypeScript, JavaScript, React, Vue, Angular, and more. Apollo Client delivers powerful caching, intuitive APIs, and comprehensive developer tools to accelerate your app development.

项目地址:https://gitcode.com/gh_mirrors/ap/apollo-client
点击查看免费下载

SchemaLink 是 Apollo Client 提供的一个终止型(terminating)ApolloLink,它不发起任何网络请求,而是将 GraphQL 操作直接交给本地的一个可执行 GraphQL Schema(如通过makeExecutableSchemabuildSchema构建)执行,并把执行结果封装成标准 Observable 返回。本篇指南以.api-reports/api-report-link_schema.api.md中定义的公开 API 为骨架,结合@apollo/client/link/schema的源码实现、单元测试与集成测试,完整讲解SchemaLink的构造选项、类型签名、执行流程,以及服务端渲染(SSR)和 Mock 数据两大核心实战场景。读完本文,你将能独立使用SchemaLink搭建"零网络请求"的 GraphQL 客户端链路,并理解其与 Apollo Client 本地状态管理的边界。

SchemaLink 是什么

在 Apollo Client 的 Link 体系里,请求会按顺序穿过一条由多个 link 组成的链(chain)。链上的 link 可以对操作进行修改、打日志、加鉴权头,但最终必须有一个终止型 link真正处理请求。HttpLink是面向远程服务器的终止型 link,而SchemaLink则是面向本地 Schema 的终止型 link:它直接在当前进程中完成 GraphQL 查询的解析与执行。

SchemaLink的定位和典型应用场景(见 docs/source/api/link/apollo-link-schema.mdx):

  • 服务端渲染(SSR):当客户端与渲染进程运行在同一台服务器上时,用SchemaLink代替网络调用,避免为每个 SSR 请求发起一次 HTTP 往返;
  • Mocking / 测试:把带 mock resolvers 的 Schema 接进客户端,使前端在真实后端就绪前就能基于真实 GraphQL 查询结构开发、联调与测试。

SchemaLink@apollo/client/link/schema子路径导出。公开导出项只有一个SchemaLink类,这一点由 src/tests/exports.ts 的快照(.api-reports校验体系)锁定:exports of public entry points @apollo/client/link/schema的导出数组仅包含字符串"SchemaLink"

import { SchemaLink } from "@apollo/client/link/schema";

公开 API 总览(API Report 解读)

.api-reports/api-report-link_schema.api.md是 API Extractor 自动生成的公开 API 报告,它完整刻画了SchemaLink的类型面。先看整体结构:

// @public export class SchemaLink extends ApolloLink { constructor(options: SchemaLink.Options); context: SchemaLink.Options["context"]; request(operation: ApolloLink.Operation): Observable<ApolloLink.Result>; rootValue: SchemaLink.Options["rootValue"]; schema: SchemaLink.Options["schema"]; validate: boolean; }

SchemaLink继承自ApolloLink(基类定义见 src/link/core/ApolloLink.ts),并拥有四个公开实例属性:schemarootValuecontextvalidate,它们与构造参数一一对应;此外覆盖了request方法作为请求处理器。request的返回类型是Observable<ApolloLink.Result>,其中ApolloLink.Resultgraphql库的FormattedExecutionResult(可含dataerrors),这说明 SchemaLink 的输出与远程 GraphQL 服务器返回的 JSON 结构完全一致。

SchemaLink.Options 选项接口

Options是构造函数唯一入参的类型,其中schema为必填项:

export interface Options { context?: SchemaLink.ResolverContext | SchemaLink.ResolverContextFunction; rootValue?: any; schema: GraphQLSchema; validate?: boolean; }

下表汇总各选项的作用、默认值与使用建议:

选项类型必填默认值作用
schemaGraphQLSchema用于执行操作的可执行 GraphQL Schema,应通过makeExecutableSchema@graphql-tools/schema)或buildSchemagraphql)创建
rootValueanyundefined传给根级 resolver 的根值(root value),大多数 Schema 用不到,仅少数高级模式需要
contextResolverContextResolverContextFunctionundefined传给所有 GraphQL resolver 的第三个参数——上下文对象;可以是静态对象,也可以是按操作动态生成上下文的函数
validatebooleanfalse是否在执行前用graphqlvalidate校验查询文档;开启后校验错误会像远程服务器一样放进结果的errors数组

两个 Context 类型

export type ResolverContext = Record<string, any>; export type ResolverContextFunction = (operation: ApolloLink.Operation) => SchemaLink.ResolverContext | PromiseLike<SchemaLink.ResolverContext>;
  • ResolverContext:普通对象,通常装数据获取连接器(data-fetching connectors)、鉴权信息以及其它请求级数据,它会被透传给每个 resolver 的第三个参数。
  • ResolverContextFunction:一个接收ApolloLink.Operation、返回上下文对象(或返回 Promise)的函数。由于它在每个操作上被调用,你可以把操作上下文(operation.getContext())里携带的 headers、variables 等信息加工进 resolver 上下文,实现"每个请求一套上下文"。源码注释里给出了典型用法(src/link/schema/index.ts):
const link = new SchemaLink({ schema, context: (operation) => { return { userId: operation.getContext().userId, dataSources: { userAPI: new UserAPI(), }, }; }, });

构造函数与内部执行流程(源码级)

构造函数实现非常直白(src/link/schema/index.ts):保存四个选项,并用!!options.validatevalidate归一化为布尔值——这也解释了为什么validate的默认值是false

核心逻辑在request方法中(src/link/schema/index.ts),它返回一个基于rxjsObservable,内部流程可以概括为四步:

  1. 解析 context:若context是函数则调用this.context(operation),否则直接使用静态对象,并用Promise.resolve包裹以统一支持同步/异步上下文;
  2. (可选)执行查询校验:若validatetrue,调用graphqlvalidate(this.schema, operation.query);一旦存在校验错误,直接返回{ errors: validationErrors }——这与真实 GraphQL 服务器的行为一致;
  3. 执行操作:调用graphqlexecute,把schemadocument(操作文档)、rootValue、解析出的contextValuevariableValues(操作变量)和operationName全部传入;
  4. 派发结果:执行成功后调用observer.next(data)observer.complete()(若 observer 未关闭);执行抛错时调用observer.error(error)
export class SchemaLink extends ApolloLink { public request(operation: ApolloLink.Operation): Observable<ApolloLink.Result> { return new Observable<ApolloLink.Result>((observer) => { new Promise<SchemaLink.ResolverContext>((resolve) => resolve( typeof this.context === "function" ? this.context(operation) : this.context ) ) .then((context) => { if (this.validate) { const validationErrors = validate(this.schema, operation.query); if (validationErrors.length > 0) { return { errors: validationErrors }; } } return execute({ schema: this.schema, document: operation.query, rootValue: this.rootValue, contextValue: context, variableValues: operation.variables, operationName: operation.operationName, }); }) .then((data) => { if (!observer.closed) { observer.next(data); observer.complete(); } }) .catch((error) => { if (!observer.closed) { observer.error(error); } }); }); } }

值得注意的细节:

  • 因为底层用的是graphqlexecute,所以SchemaLink天然支持同步执行——单元测试 src/link/schema/tests/schemaLink.ts 中专门有一条"supports query which is executed synchronously",用内省查询(introspection query)验证同步路径也能正常next+complete
  • 结果契约:resolver 抛出的错误不会以 Observable 的error通道传播,而是进入执行结果的errors数组(data相应字段为null),测试用例 "calls error when fetch fails" 断言了这一点:
    await expect(stream).toEmitTypedValue({ data: { sampleQuery: null }, errors: [{ message: "Unauthorized", path: ["sampleQuery"] }], });
  • validate开启后,对 Schema 上不存在的字段会返回形如Cannot query field "unknown" on type "Query".的校验错误(测试 "reports errors for unknown queries")。

实战一:服务端渲染(SSR)中避免网络调用

最经典的使用方式:SSR 与客户端同机时,用SchemaLink让服务端渲染直接在当前进程内执行查询。docs 中的完整示例(docs/source/api/link/apollo-link-schema.mdx):

import { ApolloClient, InMemoryCache } from "@apollo/client"; import { SchemaLink } from "@apollo/client/link/schema"; import schema from "./path/to/your/schema"; const graphqlClient = new ApolloClient({ cache: new InMemoryCache(), ssrMode: true, link: new SchemaLink({ schema }), });

要点:

  • 配合ssrMode: true使用,提示客户端处于服务端渲染模式,避免额外的重复请求;
  • link直接传入new SchemaLink({ schema }),不需要再串联HttpLink,因为SchemaLink本身就是终止型 link;
  • 渲染得到的 Apollo 状态(__APOLLO_STATE__)可随 HTML 一起注入,浏览器端再用new InMemoryCache(window.__APOLLO_STATE__)水合。

仓库的 Next.js 集成测试 integration-tests/next/src/libs/schemaLink.ts 展示了真实的落地形态:先用makeExecutableSchema({ typeDefs, resolvers })组装包含 resolver 的 Schema,再export const schemaLink = new SchemaLink({ schema })供页面使用——这个文件正是integration-tests/next中 SSR 用例的链接来源。

实战二:用 Mock resolvers 做前端开发与测试

当后端接口未就绪时,可以用graphql-tools的 mock 能力生成 schema 后交给SchemaLink。docs 中的示例:

import { ApolloClient, InMemoryCache } from '@apollo/client'; import { SchemaLink } from '@apollo/client/link/schema'; import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools'; const typeDefs = ` Query { ... } `; const mocks = { Query: () => ..., Mutation: () => ... }; const schema = makeExecutableSchema({ typeDefs }); const schemaWithMocks = addMockFunctionsToSchema({ schema, mocks }); const apolloCache = new InMemoryCache(window.__APOLLO_STATE__); const graphqlClient = new ApolloClient({ cache: apolloCache, link: new SchemaLink({ schema: schemaWithMocks }) });

这段代码的关键在于:查询仍是真实的前端查询文档,只是"执行引擎"被换成了带 mock 的本地 Schema,因此查询的字段结构、变量、别名等都会被真实校验,能提前暴露前后端字段不一致的问题。这也是为什么测试领域普遍用SchemaLink驱动基于真实查询的组件测试。

单元测试覆盖的行为契约

src/link/schema/tests/schemaLink.ts 用makeExecutableSchema构造了一个type Query { sampleQuery: Stub }的迷你 Schema,验证了以下行为,可作为你使用时的心智模型:

测试用例验证的行为
throws if no arguments given不传参数构造会抛错(schema必填)
correctly receives the constructor arguments构造参数被原样保存在公开属性上(link.schemalink.rootValue
calls next and then complete正常路径先nextcomplete
calls error when fetch failsresolver 抛错时,错误进入结果的errors,而非 Observable 的 error 通道
passes operation context into execute with context functioncontext函数按操作调用(测试断言只调用 1 次),返回值被传给 resolver
passes static context into execute静态context对象被透传给每个 resolver
reports errors for unknown queriesvalidate: true时,未知字段返回校验错误

其中"context 函数按操作调用"与"静态 context 透传"两条,直接对应Options.context的两种形态(ResolverContext | ResolverContextFunction),是你在设计多租户、按请求注入用户信息时最重要的依据。

使用注意事项与边界

  • validate的取舍:默认关闭以避免额外开销。源码注释建议在测试与开发阶段开启以尽早暴露查询错误,生产环境视 Schema 体量与性能要求决定。开启后校验错误会以errors数组形式返回,和远程服务器表现一致。
  • 包体积提醒SchemaLink依赖完整的graphql执行层,这一层体积较大。官方文档明确提示:客户端本地状态管理优先考虑 Apollo Client 的 local state 功能(与缓存集成)SchemaLink更适合 SSR 同机渲染、mock 与测试这类场景,不应作为浏览器端常规数据层的默认选择。
  • 与 Link 链的组合SchemaLink是终止型 link,你可以把它作为链的末端与其它 link 组合——例如前面接日志 link、错误重试 link,最后落到SchemaLink执行;也可以用split按条件在SchemaLinkHttpLink之间路由(比如开发环境用本地 Schema、生产环境走网络)。基类ApolloLink提供了fromsplitconcat等组合能力(见 src/link/core/ApolloLink.ts)。
  • 类型安全contextrootValue的取值自由度高(Record<string, any>any),实际项目建议用 TypeScript 泛型或类型断言收窄 resolver 的 context 类型。

总结

SchemaLink是 Apollo Client Link 体系中"在进程内执行 GraphQL"的入口:schema必填决定执行目标,rootValue支撑根级高级模式,context支持静态与按操作动态两种注入方式,validate控制是否先校验再执行。通过源码可以看到它的实现只有一次 context 解析、一次可选校验和一次graphql.execute调用,行为契约(同步执行、错误进errors、next+complete 生命周期)均有单元测试背书。对 SSR 同机渲染、前端 mock 联调与组件测试这三类需求,它都是比"起一个假 HTTP 服务"更轻量、更贴近真实 GraphQL 语义的解决方案。

延伸阅读(仓库内路径)

  • API 报告:.api-reports/api-report-link_schema.api.md
  • 源码实现:src/link/schema/index.ts
  • 单元测试:src/link/schema/tests/schemaLink.ts
  • 官方用法文档:docs/source/api/link/apollo-link-schema.mdx
  • Next.js 集成示例:integration-tests/next/src/libs/schemaLink.ts
  • Link 基类与组合能力:src/link/core/ApolloLink.ts
  • 前端
  • GraphQL

【免费下载链接】apollo-client

The industry-leading GraphQL client for TypeScript, JavaScript, React, Vue, Angular, and more. Apollo Client delivers powerful caching, intuitive APIs, and comprehensive developer tools to accelerate your app development.

项目地址:https://gitcode.com/gh_mirrors/ap/apollo-client
点击查看免费下载

相关推荐

上一篇:如何为 GitHub Enterprise Server 配置 repository cache 加速仓库克隆
下一篇:开源贡献指南:如何为react-awesome-shapes新增一个形状组件并提交你的第一个PR

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询