TypeScript strict 模式下的类型技巧

TypeScript

开启 strict: true 后遇到的常见问题和解法,包括 null 检查、泛型约束、条件类型等实用模式。


什么是 strict 模式?

tsconfig.json 中设置 "strict": true 会启用一系列严格的类型检查选项:

{
  "compilerOptions": {
    "strict": true,
    // 等同于同时启用:
    // "noImplicitAny": true,
    // "noImplicitThis": true,
    // "strictNullChecks": true,
    // "strictFunctionTypes": true,
    // "strictBindCallApply": true,
    // "strictPropertyInitialization": true,
    // "noImplicitReturns": true,
    // "alwaysStrict": true
  }
}

这确保代码的类型安全性更高,但也会产生一些新的挑战。

常见问题与解法

1. Null/Undefined 检查

最常见的问题是处理可能为 null 的值:

// ❌ strict 模式下会报错
function greet(name: string) {
  console.log(name.toUpperCase());
}

greet(null); // Error!

// ✓ 正确做法
function greet(name: string | null) {
  if (name) {
    console.log(name.toUpperCase());
  }
}

// ✓ 使用非空断言(谨慎使用)
function greet(name: string | null) {
  console.log(name!.toUpperCase());
}

2. 可选链和 Nullish Coalescing

// ✓ 可选链操作符 ?.
const user = data?.user?.name;

// ✓ Nullish Coalescing ??
const count = data?.count ?? 0;

// ✓ 组合使用
const greeting = user?.name?.toUpperCase() ?? 'Guest';

3. 泛型约束

// ❌ 太宽泛
function getValue<T>(obj: T, key: string) {
  return obj[key]; // Error: string 不能作为 T 的索引
}

// ✓ 使用 keyof 约束
function getValue<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

const user = { name: 'Jerry', age: 25 };
const name = getValue(user, 'name'); // ✓ 类型正确推导为 string
const age = getValue(user, 'age');   // ✓ 类型正确推导为 number
// getValue(user, 'email'); // Error!

4. 条件类型

// ✓ 根据输入类型返回不同类型
type Flatten<T> = T extends Array<infer U> ? U : T;

type A = Flatten<string[]>; // string
type B = Flatten<string>;   // string

// ✓ 实际应用:API 响应处理
type APIResponse<T> = T extends { data: infer D } 
  ? D 
  : T;

interface UserResponse {
  data: { id: number; name: string };
}

type UserData = APIResponse<UserResponse>; 
// { id: number; name: string }

5. readonly 修饰符

// ✓ 防止意外修改
interface User {
  readonly id: number;
  readonly name: string;
  age: number; // 可修改
}

const user: User = { id: 1, name: 'Jerry', age: 25 };
user.age = 26;    // ✓ OK
user.name = 'Li'; // ❌ Error!

// ✓ 数组的 readonly
function processArray(arr: readonly string[]) {
  arr[0] = 'test'; // ❌ Error!
  arr.push('test'); // ❌ Error!
}

6. 避免 any 的模式

// ❌ 坏习惯
function process(data: any) {
  return data.something.nested.value;
}

// ✓ 使用 unknown 和类型守卫
function process(data: unknown) {
  if (typeof data === 'object' && data !== null && 'value' in data) {
    return (data as { value: string }).value;
  }
}

// ✓ 使用类型谓词
function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'name' in obj
  );
}

if (isUser(data)) {
  console.log(data.name); // ✓ 类型已缩小为 User
}

最佳实践

  1. 使用可选链和 Nullish Coalescing:减少冗长的 if 判断
  2. 充分利用泛型:写出可复用的类型安全代码
  3. 避免 any:如果确实无法推导,用 unknown + 类型守卫
  4. 使用类型谓词:自定义类型缩小逻辑
  5. 定义清晰的接口:在函数参数和返回值上标注类型

strict 模式刚开始会让你觉得烦人,但一旦习惯了,你会发现它能提前捕获很多 bug,让代码更安全、更易维护。