小型类型级路由解析器
将路由路径解析为带类型的参数对象。
小型类型级路由解析器 是 CoddyKit 上的免费 TypeScript Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 TypeScript Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 TypeScript Academy 课程共包含 4 节课。
目标
我们要构建一个路由解析器,读取类似"users/:id/posts/:postId"的路径,并生成类型化的参数对象{ id: string; postId: string },整个过程都在编译时完成。这是类型化路由器中实际使用的一种模式。
type Params = ParseRoute<"users/:id/posts/:postId">;
// Goal: { id: string; postId: string }识别参数
当路径片段以":"开头时,它就是一个参数。我们使用模板字面量匹配此前缀,并推断冒号后的参数名称。
表示法:真实的 TypeScript 会使用由反引号包围、包含美元符号加大括号占位符的字符串来书写模板字面量类型。在这些代码片段中,我们将这种模式表示为 Tpl<...>,并按顺序列出每个部分;例如,匹配字面量前缀后跟 Rest 的反引号模板表示为 Tpl<'prefix', infer Rest>。
type ParamName<S> = S extends Tpl<':', infer Name> ? Name : never;
// Tpl<...> denotes a backtick template literal type: a colon then Name
type X = ParamName<':id'>; // 'id'
type Y = ParamName<'users'>; // never拆分路径
首先复用Split类型,按"/"将路由拆分成多个片段。随后,每个片段要么是字面量,要么是参数。
type Split<S extends string, Sep extends string> =
S extends Tpl<infer H, Sep, infer T> ? [H, ...Split<T, Sep>] : [S];
// Tpl<H, Sep, T> = a template literal type matching H, then Sep, then T
type Segs = Split<'users/:id', '/'>; // ['users', ':id']直接进行模板递归
我们也可以不先拆分,而是通过一次匹配一个片段来解析原始字符串。匹配到下一个"/"之前的内容,处理该片段,然后对剩余部分递归处理。
type ParseRoute<S extends string> =
S extends Tpl<infer Seg, '/', infer Rest>
? SegParam<Seg> & ParseRoute<Rest>
: SegParam<S>;
// Tpl<Seg, '/', Rest> matches a segment, a slash, then the rest逐片段处理参数
这个辅助类型会将单个片段转换为单属性对象(如果它是参数)或空对象(如果它是字面量)。
type SegParam<S extends string> =
S extends Tpl<':', infer Name> ? { [K in Name]: string } : {};
// Tpl<':', Name> matches a colon followed by the param name
type X = SegParam<':id'>; // { id: string }
type Y = SegParam<'posts'>; // {}使用交叉类型组合
每个片段都通过交叉类型贡献自己的参数。空对象在交叉类型中会消失,因此最终类型中只保留真正的参数。
type Params = ParseRoute<"users/:id/posts/:postId">;
// { id: string } & {} & { postId: string } & {}
// = { id: string; postId: string }跟踪解析过程
对于 "users/:id/posts/:postId":
- 片段 "users" -> {},对 ":id/posts/:postId" 递归处理
- 片段 ":id" -> { id: string },对 "posts/:postId" 递归处理
- 片段 "posts" -> {},对 ":postId" 递归处理
- 片段 ":postId" -> { postId: string },基本情况
type Final = { id: string } & { postId: string };清理结果
对象的交叉类型显示起来不太直观。一个“美化”辅助类型会使用针对其键的映射类型,将它展平为单个整洁的对象类型。
type Prettify<T> = { [K in keyof T]: T[K] } & {};
type Clean = Prettify<ParseRoute<"users/:id">>; // { id: string }使用解析器
将解析出的类型接入函数,使处理函数能够接收类型正确的参数。传入错误的键会成为编译错误。
declare function route<P extends string>(
path: P,
handler: (params: Prettify<ParseRoute<P>>) => void
): void;
route("users/:id", p => { const id: string = p.id; });类型化参数的优势
现在,编译器会直接从路由字符串中推导出params。如果重命名路径中的参数,每个处理函数所需的键都会自动更新。不需要手动编写接口,也不会出现路由与处理函数不一致的情况。
route("users/:userId", p => {
const u = p.userId; // ok
// const x = p.id; // error: id does not exist
});扩展解析器
这个小型解析器是完整类型化路由器的雏形。您可以添加类型化值(数字参数)、通配符和可选片段,所有这些都可以通过为逐片段辅助类型添加更多模板模式来实现。递归模板结构保持不变。
type SegParam2<S extends string> =
S extends Tpl<':', infer N, '(number)'> ? { [K in N]: number }
: S extends Tpl<':', infer N> ? { [K in N]: string }
: {};
// Tpl<...> stands for a backtick template literal type pattern快速检查
测试您对路由解析器的理解。
回顾
您已经构建了一个编译时路由解析器。
- 使用递归模板字面量推断匹配每个片段。
- 参数片段(
:name)会变成{ name: string }。 - 字面量片段会变成
{},并在交叉类型中消失。 Prettify会将结果展平为整洁的参数。
下一步是第 25 课:使用 tRPC 实现端到端类型安全。
用 AI 导师学习 TypeScript — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 101
- 课程
- 352
常见问题解答
「小型类型级路由解析器」课时是免费的吗?
是的 — 「小型类型级路由解析器」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 TypeScript Academy 课程的其余内容,请升级到 CoddyKit PRO。 TypeScript Academy 课程共包含 4 节课。
「小型类型级路由解析器」这节课中我会学到什么?
将路由路径解析为带类型的参数对象。 你通过在浏览器中直接运行的动手代码来练习 TypeScript Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 TypeScript Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 TypeScript Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「小型类型级路由解析器」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 TypeScript Academy 课中编写并运行代码吗?
能。每节 TypeScript Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 解析器组合子概念
- 类型级字符串拆分
- 使用模板字面量进行解析
- 小型类型级路由解析器