TypeScript 2026 实战:从类型体操到可维护工程
类型设计的三个层次
- 能编译:给 API 响应加上
interface - 能推断:用
as const+satisfies保留字面量 - 能演进:用 discriminated union 表达状态机,改一处、编译器帮你找全站
大多数项目停在第一层。本文聚焦第二、三层。
Discriminated Union:状态机的类型安全
type RequestState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; message: string };
function render<T>(state: RequestState<T>) {
switch (state.status) {
case "idle":
return null;
case "loading":
return "加载中…";
case "success":
return state.data; // TS 知道这里有 data
case "error":
return state.message;
}
}
switch 穷尽检查后,每个分支的类型会自动收窄,避免 state.data 在 loading 时误用。
satisfies:宽类型约束 + 窄字面量
const routes = {
home: "/",
blog: "/blog",
archive: "/blog/archive",
} as const satisfies Record<string, `/${string}`>;
type RouteKey = keyof typeof routes;
// routes.blog 类型为 "/blog",不是 string
比 as const 单独使用更好的是:satisfies 会校验对象是否满足 Record<string, \/${string}`>`,同时保留每个 key 的精确字面量。
模板字面量类型与 API 路径
type ApiVersion = "v1" | "v2";
type Resource = "posts" | "comments";
type ApiPath = `/api/${ApiVersion}/${Resource}`;
function fetchApi(path: ApiPath) {
return fetch(path);
}
fetchApi("/api/v1/posts"); // OK
// fetchApi("/api/v3/posts"); // 编译错误
适合 REST 客户端、路由表、权限 key 等「字符串枚举很多」的场景。
泛型默认值与 infer
从 Promise 里提取 resolved 类型:
type Awaited<T> = T extends Promise<infer U> ? U : T;
type User = Awaited<ReturnType<typeof getUser>>;
在工具类型库(如 type-fest)里常见;业务代码里更推荐 显式导出返回类型,减少间接推断链。
工程化建议
| 实践 | 说明 |
|---|---|
| 严格模式 | strict: true,逐步打开 noUncheckedIndexedAccess |
| 边界类型 | 只在 API 层 zod / valibot 解析一次,内部用窄类型 |
禁止滥用 any | ESLint @typescript-eslint/no-explicit-any |
| 公共类型包 | monorepo 用 packages/types 共享 DTO |
小结
TypeScript 的价值不在于「类型有多炫」,而在于 重构时有人替你站岗。优先把状态、路由、配置建成 discriminated union 与 satisfies 对象,比写十个 utility type 更能提升团队效率。
相关阅读
- Next.js App Router 数据获取:Server Component、缓存与流式渲染2026-05-17
- React Server Components 入门:心智模型与边界2026-05-12
- Tailwind CSS 设计令牌:用 CSS 变量构建可主题化的个人站2026-05-11
本站评论 (0)
- 暂无评论,来说第一句吧。