Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

typescript-best-practices

TypeScript 最佳实践

Apply the type-system-discipline principle skill first.

先应用 type-system-discipline principle skill。

RuleSummary
Discriminated unionsModel variants with a kind literal discriminant so impossible states can’t be represented. No optional-field bags.
Branded typesBrand primitives with & { readonly __brand: "X" } so they can’t be mixed up. Validate once at the boundary.
Constructive modelingBuild the shape so the illegal value can’t be constructed. [T, ...T[]] for non-empty, [T, T][] for even length, start plus duration for a range. Not a runtime guard, not a wish for refinement types.
Simplest total typeKeep T[] while every operation on it stays total. Strengthen to NonEmpty<T> only where the loose type forces !, a cast, or a “should never happen” throw.
unknown over anyExternal data is unknown.
Schemas before guardsBefore hand-writing a property-by-property type guard, use the repository’s runtime schema library and infer the type from the schema, such as z.infer.
No as castsEvery as is a runtime crash waiting. Cast only after validation.
Narrowing hierarchyDiscriminant switch > in operator > typeof/instanceof > user-defined type guard > as.
Type guardsMust verify the claim. A lying guard is worse than as because the bug hides behind a name that says it’s safe. Name them isX or hasX.
ExhaustivenessInline const _exhaustive: never = x; in default arms so the compiler errors when a new variant is added.
satisfies over asValidates the value without widening literal types.
Boundary validationParse where data crosses in, into a named domain type. Record<string, unknown> (however spelled) stops at that parse. Trust types inside. See the boundary-discipline principle skill.
Schema-derived typesReach for Pick/Omit/Parameters/ReturnType/Awaited/typeof before declaring a new interface.
Object argsPass objects, not positional, so argument order is self-documenting. Skip on hot paths (per-frame render, tokenizers, parsers).
Real testsDon’t mock what you can run. Prefer the framework’s real test primitives with leak/disposable checks, and verify UI in a running build. Mock only what you can’t run locally.
Structured telemetryPrefer structured logger diagnostics with enough context to debug from an id. No console.log in shipped code.
规则摘要
Discriminated unions用 kind 字面判别式建模变体,让不可能状态不可表示。不要 optional 字段袋。
Branded types用 & { readonly __brand: "X" } 给原语 branding,避免混用。在边界校验一次。
Constructive modeling建成无法构造非法值的形状。非空用 [T, ...T[]],偶长用 [T, T][],范围用 start + duration。不是运行时守卫,也不是对 refinement type 的愿望。
Simplest total type操作保持全时继续用 T[]。只在松类型逼出 !、cast 或「绝不该发生」throw 的地方加强到 NonEmpty<T>。
unknown over any外部数据是 unknown。
Schemas before guards手写逐属性 type guard 前,先用仓库的运行时 schema 库并从 schema 推断类型,如 z.infer。
No as casts每个 as 都是等着的运行时崩溃。只在校验后再 cast。
Narrowing hierarchy判别式 switch > in > typeof/instanceof > 用户 type guard > as。
Type guards必须验证主张。撒谎的 guard 比 as 更糟,因为 bug 藏在「安全」的名字后面。命名 isX 或 hasX。
Exhaustiveness在 default 臂内联 const _exhaustive: never = x;,新变体加入时编译器报错。
satisfies over as校验值且不拓宽字面量类型。
Boundary validation数据跨入处解析成命名领域类型。Record<string, unknown>(无论怎么写)停在那次解析。内部信任类型。见 boundary-discipline。
Schema-derived types声明新 interface 前先找 Pick/Omit/Parameters/ReturnType/Awaited/typeof。
Object args传对象,别传位置参数,让参数顺序自说明。热路径跳过(逐帧渲染、tokenizer、parser)。
Real tests能跑的别 mock。优先框架真实测试原语加泄漏/可释放检查,在正在跑的构建里验证 UI。只 mock 本地跑不了的。
Structured telemetry优先结构化 logger 诊断,带够从 id 调试的上下文。交付代码里禁止 console.log。

Examples: references/patterns.md.

示例见:references/patterns.md。