writing-typescript

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TypeScript Development (2025)

TypeScript开发指南(2025版)

Core Principles

核心原则

  • Strict typing: Enable all strict checks
  • Parse, don't validate: Transform untrusted data at boundaries
  • Composition over inheritance: Small, focused functions
  • Explicit over implicit: No
    any
    , prefer
    unknown
  • 严格类型检查:启用所有严格校验规则
  • 解析而非校验:在边界处转换不可信数据
  • 组合优于继承:使用小巧、单一职责的函数
  • 显式优于隐式:禁用
    any
    类型,优先使用
    unknown

Toolchain

工具链

bash
bun          # Runtime + package manager (fast)
vite         # Frontend bundling
vitest       # Testing
eslint       # Linting
prettier     # Formatting
bash
bun          # 运行时 + 包管理器(速度快)
vite         # 前端打包工具
vitest       # 测试工具
eslint       # 代码检查工具
prettier     # 代码格式化工具

Quick Patterns

常用模式

Type Guards

Type Guards

typescript
function isUser(value: unknown): value is User {
  return typeof value === "object" && value !== null && "id" in value;
}
typescript
function isUser(value: unknown): value is User {
  return typeof value === "object" && value !== null && "id" in value;
}

Discriminated Unions

Discriminated Unions

typescript
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };

function processResult<T>(result: Result<T>): T {
  if (result.ok) return result.value;
  throw result.error;
}
typescript
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };

function processResult<T>(result: Result<T>): T {
  if (result.ok) return result.value;
  throw result.error;
}

Utility Types

Utility Types

typescript
type UserUpdate = Partial<User>;
type UserSummary = Pick<User, "id" | "name">;
type UserWithoutPassword = Omit<User, "password">;
type ReadonlyUser = Readonly<User>;
typescript
type UserUpdate = Partial<User>;
type UserSummary = Pick<User, "id" | "name">;
type UserWithoutPassword = Omit<User, "password">;
type ReadonlyUser = Readonly<User>;

tsconfig.json Essentials

tsconfig.json核心配置

json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitReturns": true,
    "isolatedModules": true
  }
}
json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitReturns": true,
    "isolatedModules": true
  }
}

References

参考资料

  • PATTERNS.md - Code patterns and style
  • REACT.md - React component patterns
  • TESTING.md - Testing with vitest
  • PATTERNS.md - 代码模式与风格规范
  • REACT.md - React组件模式
  • TESTING.md - 使用vitest进行测试

Commands

常用命令

bash
bun install              # Install deps
bun run build            # Build
bun test                 # Test
bun run lint             # Lint
bun run format           # Format

bash
bun install              # 安装依赖
bun run build            # 构建项目
bun test                 # 运行测试
bun run lint             # 代码检查
bun run format           # 代码格式化

Gotchas

常见陷阱

  • import type
    with
    verbatimModuleSyntax
    and named exports
    : type-only import of a value triggers errors. Fix:
    import { type Foo } from "mod"
    (inline marker).
  • as const
    on object: readonly AND literal-narrowed
    — different from
    as Foo
    (asserts type) or
    satisfies Foo
    (validates without widening).
  • satisfies
    validates without widening;
    as
    widens
    — using
    as
    where you wanted
    satisfies
    loses literal types silently.
  • Array<T>.includes(x)
    requires
    x
    to be of type
    T
    — narrowing-from-union doesn't work; the standard fix is a type-predicate helper.
  • strictNullChecks: false
    makes
    T
    mean
    T | null | undefined
    for ALL types
    — partial migrations leave types that lie about nullability.
  • tsconfig.json
    extends
    doesn't recursively merge
    compilerOptions.paths
    — child paths REPLACE parent paths, not merge.
  • 搭配
    verbatimModuleSyntax
    使用
    import type
    和命名导出
    :对值进行仅类型导入会触发错误。修复方法:
    import { type Foo } from "mod"
    (内联标记)。
  • 在对象上使用
    as const
    :只读且字面量收窄
    — 与
    as Foo
    (断言类型)或
    satisfies Foo
    (校验但不拓宽类型)不同。
  • satisfies
    校验但不拓宽类型;
    as
    会拓宽类型
    — 在本该使用
    satisfies
    的地方使用
    as
    会悄无声息地丢失字面量类型。
  • Array<T>.includes(x)
    要求
    x
    T
    类型
    — 联合类型收窄不生效;标准修复方案是使用类型谓词助手。
  • strictNullChecks: false
    会让所有
    T
    类型等同于
    T | null | undefined
    — 部分迁移会留下无法准确反映空值情况的类型。
  • tsconfig.json
    extends
    不会递归合并
    compilerOptions.paths
    — 子配置中的paths会替换父配置的paths,而非合并。