从模式到类型的映射
了解表定义如何转换为 TypeScript 类型。
从模式到类型的映射 是 CoddyKit 上的免费 TypeScript Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 TypeScript Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 TypeScript Academy 课程共包含 4 节课。
从表定义到行类型
类型安全的 ORM 允许您一次性描述数据库表,并自动推导出其 TypeScript 行类型。您无需手写行结构;它会根据列定义计算得出。
本课使用 Drizzle 风格的 pgTable 作为贯穿示例。
定义表
表是一个将列名映射到列构建器的对象。每个构建器都包含一个 SQL 类型以及 notNull 或 primaryKey 等修饰符。
import { pgTable, serial, text, integer, boolean } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: text("name").notNull(),
age: integer("age"),
isAdmin: boolean("is_admin").notNull(),
});列类型推断
每个列构建器都携带一个描述其生成值的幽灵类型。serial 和 integer 映射为 number,text 映射为 string,boolean 映射为 boolean。
构建器还会跟踪该列是否允许为空。
import { InferSelectModel } from "drizzle-orm";
// Row type inferred from the table above
type User = InferSelectModel<typeof users>;
// {
// id: number;
// name: string;
// age: number | null; // not notNull -> nullable
// isAdmin: boolean;
// }notNull 控制可空性
行类型最重要的决定因素是 notNull()。不带它的列在推断出的类型中会变成 T | null,因为 SQL 默认允许 NULL。
const posts = pgTable("posts", {
id: serial("id").primaryKey(),
title: text("title").notNull(), // string
subtitle: text("subtitle"), // string | null
});
type Post = InferSelectModel<typeof posts>;
// { id: number; title: string; subtitle: string | null }Select 与 Insert 模型
用于读取(select)的行类型不同于用于写入(insert)的类型。在插入时,带有默认值或自动递增的列会变为可选。
import { InferInsertModel } from "drizzle-orm";
type NewUser = InferInsertModel<typeof users>;
// {
// id?: number; // serial has a default -> optional
// name: string;
// age?: number | null;
// isAdmin: boolean;
// }推断的工作原理
在底层,每个列构建器都是类似于 PgColumn<{ data: number; notNull: true }> 的泛型类。映射类型会遍历表中的每个键并读取这些标志。
// Simplified mental model of the inference
type InferRow<T> = {
[K in keyof T]: T[K] extends { _: { data: infer D; notNull: infer N } }
? N extends true ? D : D | null
: never;
};枚举与自定义类型
字符串联合列会映射为字面量联合类型,而不只是 string。这意味着无效的状态值会导致编译错误。
import { pgEnum } from "drizzle-orm/pg-core";
export const roleEnum = pgEnum("role", ["user", "admin", "owner"]);
export const members = pgTable("members", {
id: serial("id").primaryKey(),
role: roleEnum("role").notNull(), // "user" | "admin" | "owner"
});默认值
.default() 会使列在插入时变为可选,但在查询时仍保持存在。类型系统会编码“具有默认值”这一信息,因此只在适当的位置调整可选性。
const events = pgTable("events", {
id: serial("id").primaryKey(),
createdAt: text("created_at").notNull().default("now()"),
});
// Select: createdAt is string
// Insert: createdAt is optional (default fills it)为什么这很重要
由于行类型是派生的,架构变更会自动更新每个查询结果类型。重命名列后,编译器会标记所有过时的引用。不存在会发生偏移的第二个事实来源。
一个完整的小示例
综合来看:只定义一次,推断出类型,并将其作为存储库函数的契约。
type User = InferSelectModel<typeof users>;
async function findUser(id: number): Promise<User | undefined> {
// db.query returns rows already typed as User
const rows = await db.select().from(users).where(eq(users.id, id));
return rows[0];
}常见陷阱:忘记 notNull
如果您忘记在实际必需的列上使用 notNull(),推断出的类型就会变成 T | null,迫使您在整个代码中进行不必要的空值检查。请保持架构如实反映实际情况。
快速检查
请测试您对架构到类型映射的理解。
回顾
您了解到,表定义是唯一的事实来源:列构建器携带幻影类型,映射类型遍历表以构建行类型,notNull() 控制可空性,而 select 与 insert 模型在可选性方面有所不同。修改架构后,每个结果类型都会自动随之更新。
常见问题解答
「从模式到类型的映射」课时是免费的吗?
是的 — 「从模式到类型的映射」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 TypeScript Academy 课程的其余内容,请升级到 CoddyKit PRO。 TypeScript Academy 课程共包含 4 节课。
「从模式到类型的映射」这节课中我会学到什么?
了解表定义如何转换为 TypeScript 类型。 你通过在浏览器中直接运行的动手代码来练习 TypeScript Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 TypeScript Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 TypeScript Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「从模式到类型的映射」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 TypeScript Academy 课中编写并运行代码吗?
能。每节 TypeScript Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。