news 2026/9/19 7:34:33

TypeSpec 标准库分页模式完全指南:从 `@list` 到客户端/服务端驱动分页

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TypeSpec 标准库分页模式完全指南:从 `@list` 到客户端/服务端驱动分页

TypeSpec 标准库分页模式完全指南:从@list到客户端/服务端驱动分页

【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec

TypeSpec 编译器内置了一整套分页(Pagination)模式的原生支持,通过@list@pageItems@offset@nextLink@continuationToken等装饰器即可声明"列表类"操作,无需为每种分页风格手写重复的模型。本文以 pagination.md 为骨架,结合编译器源码与测试用例,系统讲解客户端驱动、服务端驱动以及二者组合的分页建模方法,并揭示这些装饰器在编译器内部如何被校验、收集与暴露给下游发射器。

分页基础:@list@pageItems

在 TypeSpec 中启用分页的第一步是两步组合:用@list装饰操作(Operation),并让操作返回类型中至少包含一个被@pageItems装饰的属性。@pageItems指向的必须是数组类型,它表示"本页返回的元素集合"。

@list op listPets(): { @pageItems pets: Pet[]; };

在 decorators.tsp 中可以看到这两个装饰器的正式声明:

  • extern dec list(target: Operation):把操作标记为返回分页列表的 list 操作;
  • extern dec pageItems(target: ModelProperty):指明包含分页元素的数组属性。

源码层面的约定更加严格。在 paging.ts 中,pageItemsDecorator的校验逻辑会检查目标属性类型是否确实为数组(isArrayModelType),否则报decorator-wrong-target诊断;而getPagingOperation(paging.ts)会强制要求返回类型中存在@pageItems属性,缺失时产生missing-paging-items错误。对应测试见 paging.test.ts。

从编译器视角看,@list只是"开启分页语义检查"的开关:validatePagingOperations会遍历程序中的每个操作,仅对标记了@list的操作调用validatePagingOperation做深度校验(paging.ts)。

客户端驱动分页:偏移量、页大小与页索引

客户端驱动(Client driven pagination)模式下,分页状态由客户端自行维护,客户端负责计算"下一页、上一页"应该请求什么参数。TypeSpec 提供 3 个装饰器标注操作参数:

装饰器含义类型要求
@pageSize每页返回的条目数量数值类型(numeric)
@offset跳过的条目数量数值类型(numeric)
@pageIndex页索引数值类型(numeric)

@offset@pageIndex并非互斥,但用途相同——都是告诉服务器"从哪一页/第几条开始取"。两者的区别在于偏移语义:@offset基于"条数跳过",@pageIndex基于"页序号"。从源码看,这三个装饰器都由createMarkerDecorator结合createNumericValidation生成(paging.ts),createNumericValidation会通过isNumericType强制校验属性类型必须是数值,例如int32int64float64等。

示例 1:固定页大小 + 偏移量

@list op listPets(@offset skip?: int32 = 0): { @pageItems pets: Pet[]; };

skip默认值为0,即默认从第一条开始。

示例 2:自定义页大小 + 偏移量

@list op listPets(@offset skip?: int32, @pageSize perPage?: int32 = 100): { @pageItems pets: Pet[]; };

perPage默认100,客户端可通过传入不同值覆盖默认页大小。

示例 3:自定义页大小 + 页索引

@list op listPets(@pageIndex page?: int32 = 1, @pageSize perPage?: int32 = 100): { @pageItems pets: Pet[]; };

@offset的"跳过条数"不同,这里的page表示从第 1 页开始逐页翻取。

服务端驱动分页:链接与续传令牌

服务端驱动(Server driven pagination)模式下,服务器在响应中返回导航信息,客户端无需自行推算页码,只需跟随服务器给出的指引。TypeSpec 提供 5 个装饰器标注响应属性:

装饰器含义典型位置
@nextLink指向下一页的链接响应体
@prevLink指向上一页的链接响应体
@firstLink指向第一页的链接响应体
@lastLink指向最后一页的链接响应体
@continuationToken续传令牌:必须同时标注在请求参数和响应属性上请求参数 + 响应体

关于@continuationToken有一个容易忽略的约束:它必须成对出现。请求参数上的@continuationToken标记"用哪个参数传递下一个令牌",响应中的@continuationToken标记"哪个属性携带服务器签发的下一个令牌"。该要求在 decorators.tsp 的注释中明确说明:"It MUST be specified both on the request parameter and the response."。

注:服务端驱动分页完全可以叠加在客户端驱动分页之上——例如既有@pageIndex/@pageSize参数,又返回@nextLink/@prevLink链接。

示例 1:HTTP 服务使用续传令牌

@list op listPets(@query @continuationToken token?: string): { @pageItems pets: Pet[]; @continuationToken nextToken?: string; };

此例中,服务器响应的 body 内有一个nextToken属性作为续传令牌。客户端取下一页时,把令牌作为 query 参数token传给下一个请求。

续传令牌也可以放在其他位置,例如 HTTP 响应头。下面的 spec 表明服务器可在响应头返回续传令牌,客户端请求下一页时仍通过 query 参数传递:

@list op listPets(@query @continuationToken token?: string): { @pageItems pets: Pet[]; @continuationToken @header nextToken?: string; };

测试用例还证实:@continuationToken的属性类型可以放宽为可空(string | null)、可选(token?: string)、可空可选(token?: string | null)甚至非字符串类型(int32),编译器均不报错(paging.test.ts)。

示例 2:HTTP 服务使用链接

@list op listPets(): { @pageItems pets: Pet[]; links: { @nextLink next?: url; @prevLink prev?: url; @firstLink first?: url; @lastLink last?: url; }; };

注意:对于 HTTP 服务,@nextLink(以及@prevLink@firstLink@lastLink)默认应通过 GET 请求跟随。链接 URI 应被视作不透明 URL,其中已包含导航到对应页面所需的全部信息。

示例 3:客户端 + 服务端驱动组合(HTTP)

@list op listPets(@query @pageIndex page?: int32 = 1, @query @pageSize perPage?: int32 = 100): { @pageItems pets: Pet[]; // 链接中会解析出携带 page 与 perPage 的完整 URL links: { @nextLink next?: url; @prevLink prev?: url; @firstLink first?: url; @lastLink last?: url; }; };

此处注释表明:链接返回的 URL 是"用当前pageperPage解析后的结果",即服务器在生成链接时会把分页参数编码进 URL。

附加参数的分页语义:哪些会带到下一页

分页操作可以携带与分页控制无关的附加参数(如过滤参数filter)。默认期望是:这些参数会被延续到下一页请求,唯一例外是链接(next/prev/first/last)场景——不同协议对"链接究竟代表什么"可能有不同解释。

对于 HTTP 链接:链接被期望为不透明的,已包含下一页 URL 所需的全部信息(query、path 参数应已编码进链接);而header 参数无法被编码进链接,因此客户端在跟随链接的后续请求中必须原样重发这些 header

场景 1:HTTP 中的 next link 分页

@route("pets") @list op listPets( @query filter?: string, @query expand?: string, @query @pageIndex page?: int32 = 1, @query @pageSize perPage?: int32 = 100, @header specialHeader?: "x-special-value", ): { @pageItems pets: Pet[]; @nextLink next?: url; };

对应的两次请求交互如下:

// 第一次请求 GET /pets?filter=dog Special-Header: x-special-value {"pets": [...], "nextLink": "/pets?filter=dog&page=2&perPage=100"} --- // 第二次请求 GET /pets?filter=dog&page=2&perPage=100 Special-Header: x-special-value {"pets": [...], "nextLink": "/pets?filter=dog&page=3&perPage=100"}

可以看到:query 参数filterpageperPage均被编码进nextLink;而specialHeader无法进入链接,因此客户端在第二次请求中显式重发该 header。

场景 2:HTTP 中的续传令牌分页

@route("pets") @list op listPets( @query filter?: string, @query expand?: string, @query @continuationToken token?: string, @header specialHeader?: "x-special-value", ): { @pageItems pets: Pet[]; @continuationToken next?: url; };
// 第一次请求 GET /pets?filter=dog Special-Header: x-special-value {"pets": [...], "continuationToken": "token2"} --- // 第二次请求 GET /pets?filter=dog&token=token2 Special-Header: x-special-value {"pets": [...], "continuationToken": "token3"}

这里filter由客户端自行延续(因为令牌模式没有服务器生成的链接),token携带服务器下发的令牌,specialHeader依旧被原样重发。

编译器内部:分页属性的收集与校验机制

理解这些装饰器如何工作,需要看编译器核心实现 paging.ts。所有分页装饰器(共 9 个)都由createMarkerDecorator工厂函数生成(paging.ts),本质是基于useStateSet的状态标记:装饰器执行时把目标属性/操作"打标",后续通过isListisOffsetPropertyisNextLink等断言函数查询。

收集阶段由getPagingOperation完成,它把分页属性分为两组(paging.ts):

  • inputoffsetpageIndexpageSizecontinuationToken(来自操作参数);
  • outputpageItemsnextLinkprevLinkfirstLinklastLinkcontinuationToken(来自返回类型)。

关键行为:

  1. 嵌套属性收集navigateProperties会递归遍历模型、联合类型及基类(baseModel)的所有属性,因此分页属性可以嵌套在返回模型的任意层级中,路径由PagingProperty.pathModelProperty[])记录,发射器可用path.map(p => p.name).join(".")还原属性路径(paging.ts)。测试见 paging.test.ts。
  2. 重复属性报错:同一操作内相同类型的分页标记(如两个@nextLink)会触发duplicate-paging-prop诊断(paging.test.ts)。
  3. 冲突标记报错:同一属性被多个不同类型的分页装饰器同时标注(如@nextLink @prevLink next: string)会触发incompatible-paging-props诊断(paging.test.ts)。
  4. 递归模型安全navigateProperties通过visited集合防止自引用模型(如selfRef?: MyPage)导致死循环(paging.test.ts)。
  5. 缺失@pageItems报错getPagingOperation在输出中没有pageItems时返回undefined并报告missing-paging-items

这些能力通过公开 APIgetPagingOperation(program, op)暴露给下游发射器,返回PagingOperation结构化数据(输入/输出两侧的分页属性及其路径),是发射器生成 OpenAPI、客户端代码或文档时的主要数据来源。

与生态的衔接:OpenAPI3 转换与既有规范

分页装饰器并非孤立存在。在 openapi3 的转换工具 中可以看到:当把已有 OpenAPI 规范转换回 TypeSpec 时,x-ms-list-page-items扩展会被映射为@pageItemsx-ms-list-continuation-token扩展会被映射为@continuationTokenx-ms-list-next-link扩展映射为@nextLink。这意味着分页元数据可以在 OpenAPI 与 TypeSpec 之间往返转换,保证语义不丢失。

小结

TypeSpec 的分页模型用一套统一装饰器覆盖了两大主流分页范式:

  • 客户端驱动@offset/@pageIndex+@pageSize,由客户端推算请求参数;
  • 服务端驱动@nextLink/@prevLink/@firstLink/@lastLink(不透明链接,HTTP 下默认 GET 跟随)与@continuationToken(请求参数与响应成对出现),由服务器下发导航信息;
  • 混合模式:两者可自由组合;附加参数默认延续到下一页,链接中已编码 query/path 参数,header 参数需客户端重发。

所有装饰器在编译器端经过严格的类型校验(数值类型、数组类型、重复与冲突检测、缺失@pageItems检测)与结构化收集,最终通过getPagingOperation供发射器消费。想要在自定义发射器中读取分页信息,直接调用getPagingOperation(program, op)即可获得完整的输入/输出分页属性清单。

【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 7:32:02

SSM+Vue构建鲜茶供销管理系统的技术实践

1. 项目背景与核心需求眉山市白果村作为川茶重要产区,当地茶农长期面临鲜茶销售渠道单一、价格波动大、中间环节多等痛点。传统线下交易模式下,茶农通常需要将鲜茶卖给中间商,经过多层流转才能到达终端经销商,导致利润被大幅压缩。…

作者头像 李华
网站建设 2026/9/19 7:29:36

x64dbg 中的 JIT 调试器设置命令 setjit/jitset 完全指南

x64dbg 中的 JIT 调试器设置命令 setjit/jitset 完全指南 【免费下载链接】x64dbg An open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis. 项目地址: https://gitcode.com/gh_mirrors/x6/x64dbg 导读:本文…

作者头像 李华
网站建设 2026/9/19 7:27:16

全栈内存泄漏治理:从页面卡顿到7×24小时稳定的实战体系

1. 这不是“修bug”,是给页面装上呼吸系统你有没有遇到过这样的场景:一个后台管理页开着一整天,早上打开时响应飞快,下午点个按钮要卡半秒,到下班前刷新一下页面直接卡死,控制台里堆满“Out of memory”报错…

作者头像 李华
网站建设 2026/9/19 7:25:21

Python+ArcGIS+随机森林:土壤类型空间预测制图实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 7:25:13

苏教版低年级数学知识点教学指南

1. 项目背景与使用场景作为一名长期辅导低年级数学的家教老师,我深知系统化知识梳理的重要性。这份苏教版数学知识点汇总最初是为自己教学备课而整理的,经过三年实际使用和不断迭代,已经成为我日常教学中最趁手的工具。现在将它分享出来&…

作者头像 李华