跪拜 Guibai
← All articles
TypeScript · React.js · Vue.js

10 TypeScript Patterns That Catch Bugs at Compile Time, Not Runtime

By GitLqr ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

TypeScript's utility types and narrower assertions prevent entire categories of bugs — missing config keys, stale type unions, and impossible prop combinations — from ever reaching runtime. Adopting these patterns means fewer manual type updates and more errors caught in the editor.

Summary

Using `satisfies` instead of `as` keeps literal types intact while still validating against an interface, catching misconfigurations that assertions silently swallow. `typeof`, `keyof`, and `as const` eliminate hand-maintained type lists and preserve exact string literals for state machines and Redux actions. Lean on inference for simple variable declarations to keep code clean.

For API and data modeling, `Record` enforces complete key-value mappings, `Partial` generates update types from a single source of truth, and `Pick`/`Omit` carve lightweight interfaces out of heavy data models without duplication. These utilities keep a single canonical interface and derive every variant from it.

On the component side, generics let a `Select` component infer item types from whatever array it receives, and discriminated union props lock out impossible combinations — a button can be a link or fire a click handler, but never both. The pattern uses `never` to make conflicting props mutually exclusive at the type level.

Takeaways
Prefer `satisfies` over `as` for object literals; it validates against the target type without widening to it, so misspelled config keys or wrong literal values fail immediately.
Use `typeof` on an existing object to derive its type instead of writing a duplicate interface.
Derive union types of property names with `keyof` so they stay in sync when the source interface changes.
Apply `as const` to constant objects to infer properties as their literal values rather than broad types like `string`.
Skip explicit type annotations on variable initializers when TypeScript can infer the type from the value.
Use `Record<Keys, ValueType>` to enforce that every key in a union gets a corresponding value.
Generate partial-update types with `Partial<T>` instead of writing a separate interface with optional fields.
Create focused types from a large interface with `Pick<T, Keys>` and `Omit<T, Keys>` rather than maintaining parallel interfaces.
Make reusable components generic so the item type flows from the caller into the render function without casting.
Model mutually exclusive props as a union of object types, marking the conflicting property as `never` in each branch.
Conclusions

The `satisfies` keyword, introduced in TypeScript 4.9, remains underused relative to `as`, yet it solves a real problem: `as` tells the compiler to trust the developer, while `satisfies` asks the compiler to verify the developer's claim.

Many TypeScript codebases still contain hand-maintained parallel types that could be replaced by `typeof`, `keyof`, `Pick`, and `Omit` — a mechanical refactor that eliminates a source of drift.

The discriminated union pattern for mutually exclusive props is a lightweight alternative to runtime prop-checking logic and works with any TypeScript version that supports union types and the `never` keyword.

Concepts & terms
satisfies
A TypeScript operator that checks an expression's type against a target type without changing the expression's inferred type. Unlike `as`, it preserves literal and narrower types while still reporting mismatches.
as const
A const assertion that tells TypeScript to infer the most specific literal type for each property, making objects deeply read-only and preventing type widening to `string` or `number`.
Record<K, V>
A utility type that constructs an object type whose keys are `K` and whose values are `V`. It enforces that every key in the union `K` is present.
Partial<T>
A utility type that makes all properties of `T` optional. Commonly used for update payloads where only a subset of fields may be submitted.
Pick<T, K> / Omit<T, K>
Utility types that create a new type by selecting (`Pick`) or excluding (`Omit`) a set of keys `K` from an existing type `T`.
Discriminated union
A union of object types where each member has a common literal property (the discriminant) or, as shown here, mutually exclusive properties marked with `never`, allowing TypeScript to narrow the type based on which properties are present.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗