typescript-strict-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTypeScript Strict Patterns
TypeScript 严格编码模式
Project setup — readwhen bootstrapping a new project or changing tsconfig / ts-reset / type-fest. ESLint baseline — readproject-setup.mdwhen adding or tweaking lint rules.eslint.config.mjs
项目设置 — 当启动新项目或修改tsconfig、ts-reset、type-fest时,请阅读。 ESLint 基准配置 — 当添加或调整 lint 规则时,请阅读project-setup.md。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 or the helper at the branch. ESLint's enforces this at lint time.
satisfies neverassertNeverdefault:switch-exhaustiveness-check将变体建模为可区分联合 — 绝不要使用一堆可选属性:
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
}
}在分支使用或辅助函数。ESLint的规则会在lint阶段强制执行这一点。
default:satisfies neverassertNeverswitch-exhaustiveness-checkBranded 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 UserIdBrand 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 errorUseful 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生产代码中禁止使用!
或as
!asNon-null assertions () and type assertions () are banned in production code. They hide type errors. Allowed in test files where the tradeoff is acceptable (enforced by ESLint config).
!asReplacements:
- Destructuring with defaults instead of :
obj.prop!const { name = '' } = config; - + nullish coalescing instead of
.at():arr[0]!const first = arr.at(0) ?? fallback; - Guard clause instead of : narrow with a type guard, then the type flows naturally.
value as Foo
生产代码中禁止使用非空断言符()和类型断言(),它们会隐藏类型错误。测试文件中允许使用,因为在测试场景下这种权衡是可接受的(由ESLint配置强制执行)。
!as替代方案:
- 带默认值的解构替代:
obj.prop!const { name = '' } = config; - + 空值合并运算符替代
.at():arr[0]!const first = arr.at(0) ?? fallback; - 守卫子句替代:通过类型守卫收窄类型,类型会自然推导。
value as Foo
Const Arrays Over Enums
优先使用Const数组而非枚举
Never use (enforced by ESLint). Use arrays with derived types:
enumas consttypescript
const STATUSES = ["pending", "active", "done"] as const;
type Status = (typeof STATUSES)[number];At system boundaries where Zod already validates, prefer — it gives you the union type and runtime validation in one step.
z.enum(STATUSES)绝不要使用(由ESLint强制执行)。使用数组并派生类型:
enumas consttypescript
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 — never duplicate a hand-written interface alongside a schema.
z.infer<>typescript
export const sessionMetaSchema = z.object({
token: z.string(),
status: z.enum(["running", "completed", "error"]),
});
export type SessionMeta = z.infer<typeof sessionMetaSchema>;- Use for data that may be corrupt (disk reads, JSONL) — skip gracefully
safeParse() - Use for startup validation (env vars) where failure is fatal
parse() - 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 , bracket access returns . Always narrow:
noUncheckedIndexedAccessT | undefined- Use — clearer intent than bracket access
.at(index) - Handle with or
if (item !== undefined)?? - Prefer ,
.find(), or destructuring over index access.filter()
启用后,方括号访问会返回。必须始终收窄类型:
noUncheckedIndexedAccessT | 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 , , . Copy the single type definition you need into with attribution. Don't add the full package as a dependency — keep the dependency graph small.
SetRequiredSimplifyJsonValuesrc/types/当严格模式导致代码冗长时,将type-fest作为参考目录使用。浏览其源码寻找、、等解决方案。将所需的单个类型定义复制到并注明出处。不要添加完整包作为依赖 — 保持依赖图精简。
SetRequiredSimplifyJsonValuesrc/types/