- 前端
- 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.
SchemaLink 是 Apollo Client 提供的一个终止型(terminating)ApolloLink,它不发起任何网络请求,而是将 GraphQL 操作直接交给本地的一个可执行 GraphQL Schema(如通过makeExecutableSchema或buildSchema构建)执行,并把执行结果封装成标准 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),并拥有四个公开实例属性:schema、rootValue、context、validate,它们与构造参数一一对应;此外覆盖了request方法作为请求处理器。request的返回类型是Observable<ApolloLink.Result>,其中ApolloLink.Result即graphql库的FormattedExecutionResult(可含data与errors),这说明 SchemaLink 的输出与远程 GraphQL 服务器返回的 JSON 结构完全一致。
SchemaLink.Options 选项接口
Options是构造函数唯一入参的类型,其中schema为必填项:
export interface Options { context?: SchemaLink.ResolverContext | SchemaLink.ResolverContextFunction; rootValue?: any; schema: GraphQLSchema; validate?: boolean; }下表汇总各选项的作用、默认值与使用建议:
| 选项 | 类型 | 必填 | 默认值 | 作用 |
|---|---|---|---|---|
schema | GraphQLSchema | 是 | 无 | 用于执行操作的可执行 GraphQL Schema,应通过makeExecutableSchema(@graphql-tools/schema)或buildSchema(graphql)创建 |
rootValue | any | 否 | undefined | 传给根级 resolver 的根值(root value),大多数 Schema 用不到,仅少数高级模式需要 |
context | ResolverContext或ResolverContextFunction | 否 | undefined | 传给所有 GraphQL resolver 的第三个参数——上下文对象;可以是静态对象,也可以是按操作动态生成上下文的函数 |
validate | boolean | 否 | false | 是否在执行前用graphql的validate校验查询文档;开启后校验错误会像远程服务器一样放进结果的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.validate把validate归一化为布尔值——这也解释了为什么validate的默认值是false。
核心逻辑在request方法中(src/link/schema/index.ts),它返回一个基于rxjs的Observable,内部流程可以概括为四步:
- 解析 context:若
context是函数则调用this.context(operation),否则直接使用静态对象,并用Promise.resolve包裹以统一支持同步/异步上下文; - (可选)执行查询校验:若
validate为true,调用graphql的validate(this.schema, operation.query);一旦存在校验错误,直接返回{ errors: validationErrors }——这与真实 GraphQL 服务器的行为一致; - 执行操作:调用
graphql的execute,把schema、document(操作文档)、rootValue、解析出的contextValue、variableValues(操作变量)和operationName全部传入; - 派发结果:执行成功后调用
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); } }); }); } }值得注意的细节:
- 因为底层用的是
graphql的execute,所以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.schema、link.rootValue) |
| calls next and then complete | 正常路径先next再complete |
| calls error when fetch fails | resolver 抛错时,错误进入结果的errors,而非 Observable 的 error 通道 |
| passes operation context into execute with context function | context函数按操作调用(测试断言只调用 1 次),返回值被传给 resolver |
| passes static context into execute | 静态context对象被透传给每个 resolver |
| reports errors for unknown queries | validate: 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按条件在SchemaLink与HttpLink之间路由(比如开发环境用本地 Schema、生产环境走网络)。基类ApolloLink提供了from、split、concat等组合能力(见 src/link/core/ApolloLink.ts)。 - 类型安全:
context与rootValue的取值自由度高(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.
相关推荐
Pixelle-Video 一句话生成 AI 短视频完整指南
Pixelle Video 一句话生成 AI 短视频完整指南 在 Pixelle Video 里输入一句"健康饮食的重要性",等上 2 5 分钟,一条成片短视频
人工智能AI 应用音视频媒体生成如何在5分钟内完成黑苹果EFI配置:OpCore-Simplify终极自动化指南
如何在5分钟内完成黑苹果EFI配置:OpCore Simplify终极自动化指南 想要体验macOS但被复杂的EFI配置吓退?OpCore Simplify正是
开发工具CLIDynamicTp架构设计与多租户线程池性能优化实践
DynamicTp架构设计与多租户线程池性能优化实践 DynamicTp是一款基于配置中心的轻量级动态线程池框架,内置监控告警功能,支持主流配置中心集成和SPI
后端任务调度可观测性
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考