typescript-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TypeScript Best Practices

TypeScript最佳实践

Type Safety

类型安全

  • Enable
    strict: true
    in
    tsconfig.json
    — never disable strict checks in production code.
  • Prefer
    unknown
    over
    any
    . If
    any
    is unavoidable, add a comment explaining why and narrow it immediately.
  • Use
    satisfies
    to validate a value matches a type while preserving its narrowed literal type:
typescript
const config = {
    endpoint: "/api/users",
    timeout: 3000,
} satisfies Config;
  • Prefer type narrowing (type guards,
    in
    operator,
    instanceof
    ) over type assertions (
    as
    ).
  • tsconfig.json
    中启用
    strict: true
    ——生产代码中绝不要禁用严格检查。
  • 优先使用
    unknown
    而非
    any
    。如果必须使用
    any
    ,添加注释说明原因并立即收窄其类型。
  • 使用
    satisfies
    验证值是否匹配类型,同时保留其收窄后的字面量类型:
typescript
const config = {
    endpoint: "/api/users",
    timeout: 3000,
} satisfies Config;
  • 优先使用类型收窄(类型守卫、
    in
    操作符、
    instanceof
    )而非类型断言(
    as
    )。

Type Inference

类型推断

  • Let TypeScript infer when the type is obvious — don't annotate what the compiler already knows:
typescript
// Redundant
const name: string = "Graham";

// Let it infer
const name = "Graham";
  • Always annotate function return types for exported/public functions — it catches accidental return type changes and improves IDE performance:
typescript
export function getUser(id: string): User | undefined {
    return users.get(id);
}
  • Annotate function parameters — they cannot be inferred from implementation.
  • 当类型显而易见时,让TypeScript自动推断——不要为编译器已能识别的类型添加注解:
typescript
// 冗余
const name: string = "Graham";

// 让编译器自动推断
const name = "Graham";
  • 对外导出/公开的函数必须标注返回类型——这能捕获意外的返回类型变更,并提升IDE性能:
typescript
export function getUser(id: string): User | undefined {
    return users.get(id);
}
  • 为函数参数添加注解——它们无法从实现中自动推断。

Types vs Interfaces

类型与接口

  • Use
    type
    for unions, intersections, mapped types, and utility types.
  • Use
    interface
    for object shapes that may be extended or implemented.
  • Be consistent within a codebase — pick one default and stick with it.
  • 使用
    type
    定义联合类型、交叉类型、映射类型和工具类型。
  • 使用
    interface
    定义可被扩展或实现的对象结构。
  • 在代码库中保持一致性——选择一种默认方式并坚持使用。

Enums and Constants

枚举与常量

  • Prefer
    as const
    objects over
    enum
    :
typescript
const Status = {
    Active: "active",
    Inactive: "inactive",
} as const;

type Status = (typeof Status)[keyof typeof Status];
  • This gives you type safety, tree-shaking, and no runtime enum overhead.
  • 优先使用
    as const
    对象而非
    enum
typescript
const Status = {
    Active: "active",
    Inactive: "inactive",
} as const;

type Status = (typeof Status)[keyof typeof Status];
  • 这种方式能提供类型安全、支持树摇,且无运行时枚举开销。

Null Handling

Null值处理

  • Prefer explicit
    | undefined
    in types over optional properties when the distinction matters.
  • Use optional chaining (
    ?.
    ) and nullish coalescing (
    ??
    ) over manual null checks.
  • Avoid non-null assertions (
    !
    ) — narrow the type instead.
  • 当需要区分时,优先在类型中显式使用
    | undefined
    而非可选属性。
  • 使用可选链(
    ?.
    )和空值合并运算符(
    ??
    )替代手动空值检查。
  • 避免使用非空断言(
    !
    )——转而收窄类型。

Generics

泛型

  • Name generic parameters descriptively when there are multiple:
    TInput
    ,
    TOutput
    instead of
    T
    ,
    U
    .
  • Constrain generics with
    extends
    to communicate intent:
typescript
function merge<T extends Record<string, unknown>>(a: T, b: Partial<T>): T {
    return { ...a, ...b };
}
  • Avoid over-genericizing — if a function only ever handles one type, don't make it generic.
  • 当存在多个泛型参数时,使用描述性名称:比如
    TInput
    TOutput
    而非
    T
    U
  • 使用
    extends
    约束泛型以传达意图:
typescript
function merge<T extends Record<string, unknown>>(a: T, b: Partial<T>): T {
    return { ...a, ...b };
}
  • 避免过度泛型化——如果函数仅处理一种类型,不要将其设为泛型。

Utility Types

工具类型

  • Use built-in utility types (
    Partial
    ,
    Required
    ,
    Pick
    ,
    Omit
    ,
    Record
    ,
    Readonly
    ) instead of reimplementing them.
  • Readonly<T>
    for data that should not be mutated.
  • Pick
    and
    Omit
    to derive subsets from existing types rather than duplicating fields.
  • 使用内置工具类型(
    Partial
    Required
    Pick
    Omit
    Record
    Readonly
    )而非自行实现。
  • 对不应被修改的数据使用
    Readonly<T>
  • 使用
    Pick
    Omit
    从现有类型派生子集,而非重复定义字段。

Error Handling

错误处理

  • Type errors explicitly — don't rely on
    catch (e)
    being
    any
    :
typescript
try {
    await fetchData();
} catch (error) {
    if (error instanceof ApiError) {
        handleApiError(error);
    }
    throw error;
}
  • Create typed error classes for domain-specific errors.
  • 显式地为错误添加类型——不要依赖
    catch (e)
    默认的
    any
    类型:
typescript
try {
    await fetchData();
} catch (error) {
    if (error instanceof ApiError) {
        handleApiError(error);
    }
    throw error;
}
  • 为领域特定错误创建带类型的错误类。

Module Organization

模块组织

  • One type/interface per concern — avoid monolithic
    types.ts
    files.
  • Co-locate types with the code that uses them.
  • Export types from barrel files only when they form part of the public API.
  • Use
    import type
    /
    export type
    for type-only imports to enable proper tree-shaking.
  • 每个关注点对应一个类型/接口——避免庞大的
    types.ts
    文件。
  • 将类型与使用它们的代码放在一起。
  • 仅当类型属于公共API时,从桶文件(barrel files)导出。
  • 使用
    import type
    /
    export type
    进行仅类型导入,以实现正确的树摇。

Naming Conventions

命名规范

  • PascalCase for types, interfaces, enums, and classes.
  • camelCase for variables, functions, and methods.
  • UPPER_SNAKE_CASE for true constants (compile-time values).
  • Don't prefix interfaces with
    I
    or types with
    T
    — it's not C#.
  • 类型、接口、枚举和类使用PascalCase命名。
  • 变量、函数和方法使用camelCase命名。
  • 真正的常量(编译时值)使用UPPER_SNAKE_CASE命名。
  • 不要为接口添加
    I
    前缀或为类型添加
    T
    前缀——这不是C#。