强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

TypeScript 开发指南 / 24 - 最佳实践

最佳实践

类型设计原则

1. 优先使用 interface 定义对象

// ✅ 推荐:使用 interface
interface User {
  id: number;
  name: string;
  email: string;
}

// ⚠️ 也可以:使用 type
type User = {
  id: number;
  name: string;
  email: string;
};

// 选择建议:
// - 对象结构 → interface(支持声明合并和 extends)
// - 联合类型、交叉类型、工具类型 → type

2. 避免使用 any

// ❌ 不好
function process(data: any): any {
  return data;
}

// ✅ 好:使用 unknown
function process(data: unknown): unknown {
  if (typeof data === "string") {
    return data.toUpperCase();
  }
  return data;
}

// ✅ 好:使用泛型
function process<T>(data: T): T {
  return data;
}

3. 使用可辨识联合代替类型断言

// ❌ 不好:使用类型断言
function handleResponse(response: any) {
  if (response.success) {
    const data = response.data as User;
    console.log(data.name);
  }
}

// ✅ 好:使用可辨识联合
type ApiResponse<T> =
  | { success: true; data: T }
  | { success: false; error: string };

function handleResponse<T>(response: ApiResponse<T>) {
  if (response.success) {
    console.log(response.data); // 自动收窄
  }
}

4. 使用 as const

// ❌ 不好:宽泛类型
const ROUTES = {
  home: "/",
  about: "/about",
  contact: "/contact"
};
// type: { home: string; about: string; contact: string }

// ✅ 好:字面量类型
const ROUTES = {
  home: "/",
  about: "/about",
  contact: "/contact"
} as const;
// type: { readonly home: "/"; readonly about: "/about"; readonly contact: "/contact" }

// 配合联合类型
type Route = typeof ROUTES[keyof typeof ROUTES]; // "/" | "/about" | "/contact"

类型体操(Type Gymnastics)

常用类型变换

// DeepPartial - 递归可选
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

// DeepReadonly - 递归只读
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

// NonNullable 递归版本
type DeepNonNullable<T> = T extends null | undefined
  ? never
  : T extends object
  ? { [K in keyof T]: DeepNonNullable<T[K]> }
  : T;

// 提取 Promise 内部类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;

// 构建元组
type BuildTuple<N extends number, T extends any[] = []> =
  T["length"] extends N ? T : BuildTuple<N, [...T, any]>;

条件类型技巧

// 类型过滤
type FilterByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K];
};

interface User {
  id: number;
  name: string;
  email: string;
  active: boolean;
}

type StringProps = FilterByType<User, string>; // { name: string; email: string }
type NumberProps = FilterByType<User, number>; // { id: number }

// 提取可选属性
type OptionalKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];

// 提取必选属性
type RequiredKeys<T> = Exclude<keyof T, OptionalKeys<T>>;

性能优化

1. 使用 type 导入

// ✅ 使用 import type(编译后被移除)
import type { User, UserRole } from "./types";
import type * as Types from "./types";

// ✅ 内联 type 导入
import { createUser, type User, type UserRole } from "./services/user";

2. 避免过度使用泛型

// ❌ 不好:过度泛型
function identity<T>(value: T): T {
  return value;
}
// 简单场景直接使用具体类型

// ✅ 好:根据需要使用泛型
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

3. 减少类型推断的复杂度

// ❌ 不好:复杂的嵌套类型推断
const result = deeply.nested.object.with.many.properties;

// ✅ 好:提取类型
type DeepType = typeof deeply.nested.object.with.many.properties;
const result: DeepType = deeply.nested.object.with.many.properties;

4. 使用 interface 而非交叉类型

// ❌ 不好:交叉类型(性能较差)
type User = { id: number } & { name: string } & { email: string };

// ✅ 好:单个 interface(性能更好)
interface User {
  id: number;
  name: string;
  email: string;
}

代码规范

命名约定

// 类型和接口:PascalCase
interface UserProfile { }
type ApiResponse = { };

// 泛型参数:单字母或 T 前缀
function identity<T>(value: T): T { }
function process<TItem, TResult>(items: TItem[]): TResult { }

// 类型别名:描述性名称
type UserId = number;
type UserMap = Record<string, User>;

// 枚举:PascalCase
enum UserRole { Admin, User, Guest }

// 常量:UPPER_SNAKE_CASE
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = "https://api.example.com";

文件组织

src/
├── types/               # 类型定义
│   ├── index.ts        # 导出
│   ├── user.ts         # 用户相关类型
│   └── api.ts          # API 相关类型
├── utils/
│   └── helpers.ts
└── index.ts

导出规范

// types/user.ts
export interface User {
  id: number;
  name: string;
  email: string;
}

export type CreateUserDto = Omit<User, "id">;
export type UpdateUserDto = Partial<CreateUserDto>;

// types/index.ts(barrel 文件)
export type { User, CreateUserDto, UpdateUserDto } from "./user";
export type { ApiResponse, PaginatedResponse } from "./api";

常见陷阱

1. 对象字面量的多余属性检查

interface User {
  name: string;
  age: number;
}

// ❌ 直接传递对象字面量会触发多余属性检查
const user: User = {
  name: "Alice",
  age: 25,
  email: "[email protected]" // ❌ 错误
};

// ✅ 通过变量传递不会触发
const userData = {
  name: "Alice",
  age: 25,
  email: "[email protected]"
};
const user: User = userData; // ✅ 兼容

2. 数组的 filter 类型收窄

const items: (string | number)[] = [1, "two", 3, "four"];

// ❌ filter 不会自动收窄类型
const strings = items.filter(item => typeof item === "string");
// 类型仍然是 (string | number)[]

// ✅ 使用类型守卫
function isString(value: unknown): value is string {
  return typeof value === "string";
}
const strings = items.filter(isString); // string[]

3. 闭包中的类型收窄

function example(value: string | null) {
  if (value !== null) {
    // value: string ✅

    setTimeout(() => {
      // ❌ value 可能为 null(闭包问题)
      // console.log(value.toUpperCase());

      // ✅ 解决:捕获到局部变量
      const safeValue = value;
      console.log(safeValue.toUpperCase());
    }, 100);
  }
}

4. 类型断言的滥用

// ❌ 不好:随意使用 as
const data = JSON.parse(str) as User;

// ✅ 好:验证后使用
const parsed = JSON.parse(str);
if (
  typeof parsed === "object" &&
  parsed !== null &&
  "name" in parsed &&
  "email" in parsed
) {
  const user = parsed as User;
}

5. 枚举的陷阱

// ❌ 数字枚举不安全
enum Status {
  Active,    // 0
  Inactive,  // 1
}

const s: Status = 42; // ✅ 编译通过(任何数字都兼容)

// ✅ 字符串枚举更安全
enum Status {
  Active = "ACTIVE",
  Inactive = "INACTIVE",
}

const s: Status = "ACTIVE"; // ✅
// const s: Status = "INVALID"; // ❌

ESLint 配置

npm install -D @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint
// .eslintrc.json
{
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "rules": {
    "@typescript-eslint/no-explicit-any": "warn",
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/no-non-null-assertion": "warn"
  }
}

业务场景:类型安全的配置管理

// 配置类型
interface AppConfig {
  server: {
    port: number;
    host: string;
    ssl: boolean;
  };
  database: {
    url: string;
    pool: number;
  };
  auth: {
    secret: string;
    expiresIn: string;
  };
}

// 验证配置
function validateConfig(config: unknown): AppConfig {
  if (typeof config !== "object" || config === null) {
    throw new Error("Config must be an object");
  }

  const c = config as Record<string, unknown>;

  // 验证每个字段
  if (typeof c.server !== "object") throw new Error("Missing server config");
  // ... 更多验证

  return config as AppConfig;
}

// 使用
const config = validateConfig(JSON.parse(
  await fs.readFile("config.json", "utf-8")
));

注意事项

  1. 类型是文档——好的类型定义是最好的文档
  2. 不要过度类型化——简单场景不需要复杂类型
  3. 优先使用 unknown 而非 any
  4. as const 可以让值变为类型
  5. 定期运行 tsc --noEmit 检查类型错误

扩展阅读