在类型级别进行解析
TypeScript 的模板文字类型与递归条件类型和 infer 相结合,允许您完全在类型系统内解析和转换字符串文字。这不是一个运行时解析器,它是一个编译时解析器,可以从字符串常量中提取结构,从而为 URL 路由、SQL 查询、命令行参数等启用强类型 API。
模板文字类型入门
模板文字类型可以插入其他类型并推断字符串的一部分。
type Hello<T extends string> = `hello ${T}`;
type Result = Hello<"world">; // "hello world"
在条件类型中使用 infer ,您可以提取子字符串:
type ExtractName<T extends string> =
T extends `hello ${infer Name}` ? Name : never;
type A = ExtractName<"hello alice">; // "alice"
type B = ExtractName<"goodbye bob">; // never
解析URL路由参数
一个常见的用例是从路由模式中提取路径参数:
type RouteParams<T extends string> =
T extends `${infer _Start}:${infer Param}/${infer Rest}`
? { [K in Param | keyof RouteParams<Rest>]: string }
: T extends `${infer _Start}:${infer Param}`
? { [K in Param]: string }
: {};
这会在每个 : 前缀处递归地分割字符串并构建一个对象类型。
type Params = RouteParams<"/users/:id/posts/:postId">;
// { id: string; postId: string }
更强大的路由解析器
上面的简单版本因可选段或正则表达式约束而中断。生产级解析器处理这些边缘情况:
type ParseParam<T extends string> =
T extends `${infer _Prefix}:${infer Param}`
? Param extends `${infer Name}?`
? { [K in Name]?: string }
: { [K in Name]: string }
: {};
type Merge<T, U> = { [K in keyof T | keyof U]: K extends keyof T ? T[K] : K extends keyof U ? U[K] : never };
type ParseRoute<T extends string> =
T extends `${infer Segment}/${infer Rest}`
? Merge<ParseParam<Segment>, ParseRoute<Rest>>
: ParseParam<T>;
可选参数 (?) 成为结果类型中的可选属性。
提取查询字符串
查询字符串解析遵循相同的模式:
type ParseQuery<T extends string> =
T extends `${infer First}&${infer Rest}`
? Merge<ParseSingle<First>, ParseQuery<Rest>>
: ParseSingle<T>;
type ParseSingle<T extends string> =
T extends `${infer Key}=${infer Value}`
? { [K in Key]: Value }
: {};
type Q = ParseQuery<"page=1&limit=10&sort=asc">;
// { page: "1"; limit: "10"; sort: "asc" }
对于实际使用,结合映射类型来转换值:
type ParseInt<T extends string> =
T extends `${infer N extends number}` ? N : never;
type TypedQuery = {
[K in keyof Q]: K extends "page" | "limit" ? ParseInt<Q[K]> : Q[K];
};
// { page: 1; limit: 10; sort: "asc" }
实用的 API 类型模式
当您将解析器绑定到 API 函数时,真正的力量就会显现出来:
function get<T extends string>(
path: T,
params: ParseRoute<T>
): void;
get("/users/:id/posts/:postId", {
id: "42",
postId: "99"
}); // OK
get("/users/:id", {
id: "42",
extra: "x"
});
// Error: 'extra' does not exist in { id: string }
typed-query-parser、ts-toolbelt 和 Hono 等库使用这些技术来提供端到端类型安全,而无需运行时开销。
局限性
| 问题 | 影响 |
|---|---|
| 深度递归限制(~50 级) | 在很长的琴弦上断裂 |
| 没有正则表达式支持 | 无法验证像 /^[a-z]+$/ 这样的模式 |
| 没有工会分裂 | 难以解析 `a |
| 编译时成本 | 复杂的解析器会减慢 tsc |
概括
类型级解析器在编译时将字符串文字转换为结构化类型。它们支持完全类型化的 URL 路由、查询字符串提取和配置解析,而无需生成代码。使用带有 infer 的递归条件类型和模板文字来逐个字符地分离字符串。密切关注递归深度和编译时性能,但对于典型的路由用例,该模式是生产就绪的。

