·前端开发·5 分钟阅读

TypeScript 2026 实战:从类型体操到可维护工程

分享X微博

类型设计的三个层次

  1. 能编译:给 API 响应加上 interface
  2. 能推断:用 as const + satisfies 保留字面量
  3. 能演进:用 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.dataloading 时误用。

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 解析一次,内部用窄类型
禁止滥用 anyESLint @typescript-eslint/no-explicit-any
公共类型包monorepo 用 packages/types 共享 DTO

小结

TypeScript 的价值不在于「类型有多炫」,而在于 重构时有人替你站岗。优先把状态、路由、配置建成 discriminated union 与 satisfies 对象,比写十个 utility type 更能提升团队效率。

相关阅读

本站评论 (0)

  • 暂无评论,来说第一句吧。