typescript-strict-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TypeScript Strict Patterns

TypeScript 严格编码模式

Project setup — read
project-setup.md
when bootstrapping a new project or changing tsconfig / ts-reset / type-fest. ESLint baseline — read
eslint.config.mjs
when adding or tweaking lint rules.
项目设置 — 当启动新项目或修改tsconfig、ts-reset、type-fest时,请阅读
project-setup.md
ESLint 基准配置 — 当添加或调整 lint 规则时,请阅读
eslint.config.mjs

Discriminated Unions + Exhaustive Checking

可区分联合 + 穷尽性检查

Model variants as discriminated unions — never bags of optional properties:
typescript
// GOOD — each variant carries exactly its data
type Result =
  | { status: "ok"; data: string }
  | { status: "error"; message: string };

// Exhaustive check helper — will fail to compile if a variant is missed
function assertNever(x: never): never {
  throw new Error(`Unexpected: ${JSON.stringify(x)}`);
}

function handle(r: Result) {
  switch (r.status) {
    case "ok": return r.data;
    case "error": return r.message;
    default: assertNever(r); // compile error if a case is missing
  }
}
Use
satisfies never
or the
assertNever
helper at the
default:
branch. ESLint's
switch-exhaustiveness-check
enforces this at lint time.
将变体建模为可区分联合 — 绝不要使用一堆可选属性:
typescript
// GOOD — each variant carries exactly its data
type Result =
  | { status: "ok"; data: string }
  | { status: "error"; message: string };

// Exhaustive check helper — will fail to compile if a variant is missed
function assertNever(x: never): never {
  throw new Error(`Unexpected: ${JSON.stringify(x)}`);
}

function handle(r: Result) {
  switch (r.status) {
    case "ok": return r.data;
    case "error": return r.message;
    default: assertNever(r); // compile error if a case is missing
  }
}
default:
分支使用
satisfies never
assertNever
辅助函数。ESLint的
switch-exhaustiveness-check
规则会在lint阶段强制执行这一点。

Branded Types

品牌类型

Prevent accidental interchange of structurally identical types with a brand:
typescript
type Brand<T, B extends string> = T & { readonly __brand: B };

type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;

function getUser(id: UserId) { /* ... */ }

const uid = "abc" as UserId; // cast once at the boundary
getUser(uid);    // OK
getUser("abc");  // compile error — plain string is not UserId
Brand at system boundaries (API response parsing, DB reads). Internal code then carries the brand without further casts.
通过品牌防止结构相同的类型被意外混用:
typescript
type Brand<T, B extends string> = T & { readonly __brand: B };

type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;

function getUser(id: UserId) { /* ... */ }

const uid = "abc" as UserId; // cast once at the boundary
getUser(uid);    // OK
getUser("abc");  // compile error — plain string is not UserId
在系统边界(API响应解析、数据库读取)处添加品牌标记。内部代码随后可携带该品牌,无需再次转换。

Template Literal Types

模板字面量类型

Enforce string formats at the type level:
typescript
type HexColor = `#${string}`;
type Route = `/${string}`;
type EventName = `on${Capitalize<string>}`;

function setColor(c: HexColor) { /* ... */ }
setColor("#ff0000"); // OK
setColor("red");     // compile error
Useful for config keys, route paths, and event names where runtime validation is overkill but typos are common.
在类型层面强制字符串格式:
typescript
type HexColor = `#${string}`;
type Route = `/${string}`;
type EventName = `on${Capitalize<string>}`;

function setColor(c: HexColor) { /* ... */ }
setColor("#ff0000"); // OK
setColor("red");     // compile error
适用于配置键、路由路径和事件名称等场景,这些场景中运行时验证有些过度,但拼写错误很常见。

No
!
or
as
in Production Code

生产代码中禁止使用
!
as

Non-null assertions (
!
) and type assertions (
as
) are banned in production code. They hide type errors. Allowed in test files where the tradeoff is acceptable (enforced by ESLint config).
Replacements:
  • Destructuring with defaults instead of
    obj.prop!
    :
    const { name = '' } = config;
  • .at()
    + nullish coalescing
    instead of
    arr[0]!
    :
    const first = arr.at(0) ?? fallback;
  • Guard clause instead of
    value as Foo
    : narrow with a type guard, then the type flows naturally.
生产代码中禁止使用非空断言符(
!
)和类型断言(
as
),它们会隐藏类型错误。测试文件中允许使用,因为在测试场景下这种权衡是可接受的(由ESLint配置强制执行)。
替代方案:
  • 带默认值的解构替代
    obj.prop!
    const { name = '' } = config;
  • .at()
    + 空值合并运算符
    替代
    arr[0]!
    const first = arr.at(0) ?? fallback;
  • 守卫子句替代
    value as Foo
    :通过类型守卫收窄类型,类型会自然推导。

Const Arrays Over Enums

优先使用Const数组而非枚举

Never use
enum
(enforced by ESLint). Use
as const
arrays with derived types:
typescript
const STATUSES = ["pending", "active", "done"] as const;
type Status = (typeof STATUSES)[number];
At system boundaries where Zod already validates, prefer
z.enum(STATUSES)
— it gives you the union type and runtime validation in one step.
绝不要使用
enum
(由ESLint强制执行)。使用
as const
数组并派生类型:
typescript
const STATUSES = ["pending", "active", "done"] as const;
type Status = (typeof STATUSES)[number];
在Zod已进行验证的系统边界处,优先使用
z.enum(STATUSES)
— 它能一步提供联合类型和运行时验证。

Zod Schemas at System Boundaries

系统边界处的Zod模式

Use Zod schemas as the single source of truth for data crossing system boundaries (disk I/O, env vars, API responses, config files). Derive types with
z.infer<>
— never duplicate a hand-written interface alongside a schema.
typescript
export const sessionMetaSchema = z.object({
  token: z.string(),
  status: z.enum(["running", "completed", "error"]),
});
export type SessionMeta = z.infer<typeof sessionMetaSchema>;
  • Use
    safeParse()
    for data that may be corrupt (disk reads, JSONL) — skip gracefully
  • Use
    parse()
    for startup validation (env vars) where failure is fatal
  • Skip Zod for internal function arguments between trusted modules and for SDK-owned types
使用Zod模式作为跨系统边界(磁盘I/O、环境变量、API响应、配置文件)数据的唯一可信来源。通过
z.infer<>
派生类型 — 绝不要在模式旁重复手写接口。
typescript
export const sessionMetaSchema = z.object({
  token: z.string(),
  status: z.enum(["running", "completed", "error"]),
});
export type SessionMeta = z.infer<typeof sessionMetaSchema>;
  • 对于可能损坏的数据(磁盘读取、JSONL),使用
    safeParse()
    — 优雅跳过错误
  • 对于启动时的验证(环境变量),使用
    parse()
    — 验证失败则终止程序
  • 可信模块间的内部函数参数和SDK自有类型无需使用Zod

Safe Indexed Access

安全索引访问

With
noUncheckedIndexedAccess
, bracket access returns
T | undefined
. Always narrow:
  • Use
    .at(index)
    — clearer intent than bracket access
  • Handle with
    if (item !== undefined)
    or
    ??
  • Prefer
    .find()
    ,
    .filter()
    , or destructuring over index access
启用
noUncheckedIndexedAccess
后,方括号访问会返回
T | undefined
。必须始终收窄类型:
  • 使用
    .at(index)
    — 比方括号访问的意图更清晰
  • 通过
    if (item !== undefined)
    ??
    处理可能的undefined
  • 优先使用
    .find()
    .filter()
    或解构而非索引访问

Type Helpers (type-fest as Inspiration)

类型辅助工具(以type-fest为参考)

Use type-fest as a reference catalog when strict patterns make code verbose. Browse its source for solutions like
SetRequired
,
Simplify
,
JsonValue
. Copy the single type definition you need into
src/types/
with attribution. Don't add the full package as a dependency — keep the dependency graph small.
当严格模式导致代码冗长时,将type-fest作为参考目录使用。浏览其源码寻找
SetRequired
Simplify
JsonValue
等解决方案。将所需的单个类型定义复制到
src/types/
并注明出处。不要添加完整包作为依赖 — 保持依赖图精简。