0Pricing
TypeScript Academy · 课时

从模式到类型的映射

了解表定义如何转换为 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 从模式到类型的映射
  2. 类型安全的查询构建
  3. 推断查询结果结构
  4. 带类型的关系与连接
← 返回 TypeScript Academy