先记住这个答案
模板字面量类型可以把字符串拆成固定片段与 infer 捕获片段,再用条件类型提取命中的内容。例如针对 /users/:id 这样的静态路由模板,可以从冒号后捕获参数名,并拒绝空名或多余路径段。输入如果只是宽 string,编译器没有足够证据知道它一定符合这个模板。这样的工具只处理已经声明的有限语法,不会在运行时解析 URL,也不会自动支持可选参数、重复参数、编码或查询字符串。完整路由库应让类型规则与实际解析协议一致。
- 固定片段限定匹配范围,infer 捕获剩余信息
- 宽 string 不提供具体路径模板的静态证据
- 支持的路由语法必须与运行解析规则对齐
从固定模式提取一个名字
在类型层识别某种路由模板时,固定前缀能让规则保持简单可解释。匹配成功后,infer 得到对应字符串片段,再根据空名和斜杠检查决定是否接受。这样比直接捕获所有后缀并称为合法参数更诚实。
示例只支持用户路径后的一个参数名,额外段和没有名称的模板返回 never。它处理的是声明模板 /users/:id,而不是访问地址 /users/7;把声明语法与真实 URL 混在一起,会让工具的结果和运行解析行为出现根本错位。
type UserParam<Path extends string> =
Path extends `/users/:${infer Name}`
? Name extends '' ? never
: Name extends `${string}/${string}` ? never : Name
: never;
type IdName = UserParam<'/users/:id'>;
type EmptyName = UserParam<'/users/:'>;
type ExtraSegment = UserParam<'/users/:id/posts'>;
type DynamicInput = UserParam<string>;
const key: IdName = 'id';
// @ts-expect-error 当前模板捕获的是 id
const wrongKey: IdName = 'userId';IdName 得到 id,空名、额外路径段和宽字符串输入都没有得到匹配结果。示例只检查这几项语法,参数名字中其他字符是否合法仍需要由更完整的路由协议决定。
为什么捕获结果不总是想象中的片段
模板匹配按照类型系统支持的字符串结构工作,不会自动理解框架中的冒号、星号、问号或编码规则。若没有额外限制,某个 infer 可能捕获包含这些字符的整个后缀。必须先定义支持的语法,再判断模式是否真的排除了不允许输入。
多个参数和嵌套路径通常需要进一步拆分或递归,但递归深度与联合规模都会影响编译成本。初始工具不必假装支持所有情况,可以明确拒绝或不处理超出范围的模板,并将复杂协议交给经过验证的路由库类型。
静态模板与动态地址怎样连接
路由声明若以宽 string 变量保存,具体片段信息可能已经丢失。可以在受控定义处保留字面量,再由工具派生参数键;但网络输入的地址仍需通过实际解析器检查,不能通过断言把任意字符串伪装成合法模板。
参数名提取也不等于参数值验证。得到 id 这个键,并不能证明访问地址里的值是数字、有效编号或有权限访问的资源。运行时需要解码、格式校验与业务检查,类型工具只负责让静态声明和调用接口更少出现拼写或结构不一致。
容易答错的地方
- infer 捕获到后缀就证明路由语法合法
- 捕获只是模式匹配结果,未声明的问号、编码或重复参数规则不会自动验证;应把工具支持的输入范围与解析器协议写清楚。
- 类型提取可以替代真实 URL 的解析
- 类型信息在运行时消失,动态地址仍需要解析、解码和校验;静态模板参数名与用户实际提交的参数值是两个不同层面。
面试官还会怎么问?
宽 string 为什么不能得到具体 id 类型?
编译器只知道它可能是任意字符串,无法保证符合固定模板;要得到具体参数名,需要保留路由声明的字面量信息或先建立受控映射。
捕获参数名后就能把参数值当数字吗?
不能,名字只是一个键,值类型还需要路由契约或解析器明确规定;实际 URL 字符串必须经过相应格式与业务有效性检查。
支持多个参数时可以继续递归拆分吗?
可以设计相应类型,但要与运行解析语法保持一致,并验证复杂度、空段和重复参数等边界;不要无限扩展一个未经检验的简短模板。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。