- Published on
- 约 1030 字
从零理解 TS 类型编程:以提取数据库列名为例
- Authors

- Name
- 小辉辉
在编写类型安全的 SQL 查询构建器(Query Builder)或 ORM 框架时,我们经常能看到类似下面这样极其精妙的 TypeScript 类型定义:
export type AnyColumn<DB, TB extends keyof DB> = {
[T in TB]: keyof DB[T];
}[TB] & string;
这段代码虽然简短,却完美融合了 TypeScript 高级类型编程中的多个核心概念。它的主要作用是:从数据库类型 DB 指定的若干张表(TB)中,提取出所有列名(字段名)的联合类型。为了彻底搞懂它的底层逻辑,我们可以通过一个具体的例子,将其拆解为四个步骤来逐步推导。
前置假设与例子
假设我们有一个描述数据库结构的 DB 接口,以及一个代表表名的联合类型 'users':
interface DB {
users: {
id: number;
name: string;
email: string;
};
posts: {
title: string;
content: string;
};
}
// 假设我们传入的泛型参数如下:
// DB = 上面的 DB 接口
// TB = 'users'
第一步:映射类型遍历表名 { [T in TB]: ... }
代码中的 { [T in TB]: keyof DB[T]; } 使用了映射类型(Mapped Types)。这类似于在 JavaScript 中对一个数组执行 map 操作,或者在对象上执行 for...in 循环。
它会遍历联合类型 TB 中的每一个表名(作为键 T),并生成一个全新的对象类型。因为我们的 TB 是 'users',所以它会生成一个只包含 users 属性的临时对象框架:
// 第一步的中间产物(伪代码)
{
users: ... // 等待计算
}
注:如果 TB 传入的是 'users' | 'posts',这里就会生成 { users: ...; posts: ... }。
第二步:提取列名 keyof DB[T]
接下来看映射类型中的值部分:keyof DB[T]。这里 T 此时代表 'users',那么 DB[T] 就是 DB['users'],也就是 users 表的行结构(即 { id: number; name: string; email: string; })。
keyof 是 TypeScript 的索引类型查询操作符,它会提取出该对象所有键的联合类型。因此,keyof DB['users'] 的结果就是 'id' | 'name' | 'email'。
结合第一步,此时整个映射类型的完整结果是:
{
users: 'id' | 'name' | 'email'
}
第三步:索引访问取联合值 }[TB]
代码紧接着使用了 }[TB],这属于索引访问类型(Indexed Access Types),也叫 Lookup Types。它的作用是从上一步生成的临时对象中,再次根据 TB(即 'users')去提取对应的值。
也就是执行:{ users: 'id' | 'name' | 'email' }['users']。这一步操作会直接“拆包”,取出里面的值,最终得到 'id' | 'name' | 'email'。
如果 TB 是多张表的联合(如 'users' | 'posts'),这一步就会取出两张表的所有列名,并自动合并成一个巨大的联合类型(例如 'id' | 'name' | 'email' | 'title' | 'content')。
第四步:约束为字符串 & string
最后,代码使用了 & string。这是**交叉类型(Intersection Types)**的应用,目的是将前面的结果与 string 取交集。
在 JavaScript 中,对象的键除了 string,还可能是 number 或 symbol。加上 & string 是一个防御性的编程技巧,它可以过滤掉可能存在的数字或符号类型的键,确保最终得到的列名绝对是字符串类型。
推导结果为:('id' | 'name' | 'email') & string,最终结果依然是 'id' | 'name' | 'email'。这样做能极大避免在后续进行字符串拼接或生成 SQL 语句时出现意外的类型报错。
总结
整个 AnyColumn 类型的执行流程可以概括为:遍历指定的表名 -> 提取每张表里的所有字段名 -> 合并这些字段名为一个联合类型 -> 过滤并确保字段名都是字符串。
最终,AnyColumn<DB, 'users'> 的类型就变成了 'id' | 'name' | 'email'。这种写法能够让开发者在编写数据库查询代码时,获得极其精准的字段名自动提示(IntelliSense),并在拼写错误时立刻得到编译器的警告,是构建高健壮性 Node.js 后端项目的利器。
