typescript-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TypeScript Best Practices

TypeScript最佳实践

Follows type-first, functional, and error handling patterns from CLAUDE.md. This skill covers language-specific idioms only.
遵循CLAUDE.md中的类型优先、函数式编程和错误处理模式。本技能仅涵盖特定语言的惯用写法。

Pair with React Best Practices

搭配React最佳实践使用

When working with React components (
.tsx
,
.jsx
files or
@react
imports), always load
react-best-practices
alongside this skill. This skill covers TypeScript fundamentals; React-specific patterns (effects, hooks, refs, component design) are in the dedicated React skill.
在处理React组件(
.tsx
.jsx
文件或
@react
导入)时,请始终同时加载
react-best-practices
技能。本技能涵盖TypeScript基础内容;React特定模式(副作用、hooks、refs、组件设计)在专门的React技能中介绍。

Make Illegal States Unrepresentable

使非法状态无法被表示

Use the type system to prevent invalid states at compile time.
Discriminated unions for mutually exclusive states:
ts
// Good: only valid combinations possible
type RequestState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

// Bad: allows invalid combinations like { loading: true, error: Error }
type RequestState<T> = {
  loading: boolean;
  data?: T;
  error?: Error;
};
Branded types for domain primitives:
ts
type UserId = string & { readonly __brand: 'UserId' };
type OrderId = string & { readonly __brand: 'OrderId' };

// Compiler prevents passing OrderId where UserId expected
function getUser(id: UserId): Promise<User> { /* ... */ }
Const assertions for literal unions:
ts
const ROLES = ['admin', 'user', 'guest'] as const;
type Role = typeof ROLES[number]; // 'admin' | 'user' | 'guest'

// Array and type stay in sync automatically
function isValidRole(role: string): role is Role {
  return ROLES.includes(role as Role);
}
Exhaustive switch with never check:
ts
type Status = "active" | "inactive";

function processStatus(status: Status): string {
  switch (status) {
    case "active":
      return "processing";
    case "inactive":
      return "skipped";
    default: {
      const _exhaustive: never = status;
      throw new Error(`unhandled status: ${_exhaustive}`);
    }
  }
}
使用类型系统在编译时防止无效状态。
使用可辨识联合处理互斥状态:
ts
// Good: only valid combinations possible
type RequestState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

// Bad: allows invalid combinations like { loading: true, error: Error }
type RequestState<T> = {
  loading: boolean;
  data?: T;
  error?: Error;
};
为领域原语使用品牌化类型:
ts
type UserId = string & { readonly __brand: 'UserId' };
type OrderId = string & { readonly __brand: 'OrderId' };

// Compiler prevents passing OrderId where UserId expected
function getUser(id: UserId): Promise<User> { /* ... */ }
为字面量联合使用const断言:
ts
const ROLES = ['admin', 'user', 'guest'] as const;
type Role = typeof ROLES[number]; // 'admin' | 'user' | 'guest'

// Array and type stay in sync automatically
function isValidRole(role: string): role is Role {
  return ROLES.includes(role as Role);
}
带never检查的穷尽式switch语句:
ts
type Status = "active" | "inactive";

function processStatus(status: Status): string {
  switch (status) {
    case "active":
      return "processing";
    case "inactive":
      return "skipped";
    default: {
      const _exhaustive: never = status;
      throw new Error(`unhandled status: ${_exhaustive}`);
    }
  }
}

Runtime Validation with Zod

使用Zod进行运行时验证

  • Define schemas as single source of truth; infer TypeScript types with
    z.infer<>
    . Avoid duplicating types and schemas.
  • Use
    safeParse
    for user input where failure is expected; use
    parse
    at trust boundaries where invalid data is a bug.
  • Compose schemas with
    .extend()
    ,
    .pick()
    ,
    .omit()
    ,
    .merge()
    for DRY definitions.
  • Add
    .transform()
    for data normalization at parse time (trim strings, parse dates).
ts
import { z } from "zod";

const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  name: z.string().min(1),
  createdAt: z.string().transform((s) => new Date(s)),
});

type User = z.infer<typeof UserSchema>;

// Strict parsing at trust boundaries — throws if API contract violated
export async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  if (!response.ok) {
    throw new Error(`fetch user ${id} failed: ${response.status}`);
  }
  return UserSchema.parse(await response.json());
}

// Caller handles both success and error from user input
const result = UserSchema.safeParse(formData);
if (!result.success) {
  setErrors(result.error.flatten().fieldErrors);
  return;
}
  • 将Schema定义作为单一事实来源;使用
    z.infer<>
    推导TypeScript类型。避免重复定义类型和Schema。
  • 在预期可能失败的用户输入场景中使用
    safeParse
    ;在信任边界处使用
    parse
    ,此处无效数据属于bug。
  • 使用
    .extend()
    .pick()
    .omit()
    .merge()
    组合Schema,实现DRY(Don't Repeat Yourself)定义。
  • 添加
    .transform()
    在解析时进行数据标准化(如修剪字符串、解析日期)。
ts
import { z } from "zod";

const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  name: z.string().min(1),
  createdAt: z.string().transform((s) => new Date(s)),
});

type User = z.infer<typeof UserSchema>;

// Strict parsing at trust boundaries — throws if API contract violated
export async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  if (!response.ok) {
    throw new Error(`fetch user ${id} failed: ${response.status}`);
  }
  return UserSchema.parse(await response.json());
}

// Caller handles both success and error from user input
const result = UserSchema.safeParse(formData);
if (!result.success) {
  setErrors(result.error.flatten().fieldErrors);
  return;
}

Optional: type-fest

可选工具:type-fest

For advanced type utilities beyond TypeScript builtins, consider type-fest:
  • Opaque<T, Token>
    - cleaner branded types than manual
    & { __brand }
    pattern
  • PartialDeep<T>
    - recursive partial for nested objects
  • ReadonlyDeep<T>
    - recursive readonly for immutable data
  • SetRequired<T, K>
    /
    SetOptional<T, K>
    - targeted field modifications
  • Simplify<T>
    - flatten complex intersection types in IDE tooltips
ts
import type { Opaque, PartialDeep } from 'type-fest';

type UserId = Opaque<string, 'UserId'>;
type UserPatch = PartialDeep<User>;
对于TypeScript内置工具之外的高级类型工具,可以考虑type-fest
  • Opaque<T, Token>
    - 比手动
    & { __brand }
    模式更简洁的品牌化类型
  • PartialDeep<T>
    - 针对嵌套对象的递归Partial类型
  • ReadonlyDeep<T>
    - 针对不可变数据的递归Readonly类型
  • SetRequired<T, K>
    /
    SetOptional<T, K>
    - 针对性的字段修改
  • Simplify<T>
    - 在IDE提示中展平复杂的交叉类型
ts
import type { Opaque, PartialDeep } from 'type-fest';

type UserId = Opaque<string, 'UserId'>;
type UserPatch = PartialDeep<User>;