折腾侠
技术教程

TypeScript 高级类型技巧:写出更安全的代码

深入探讨 TypeScript 条件类型、映射类型、类型守卫、泛型约束等高级技巧,通过实战案例展示如何编写更安全、更可维护的代码。

折腾侠
2026/03/16 发布
10约 5 分钟772 字 / 574 词00

TypeScript 高级类型技巧:写出更安全的代码

引言

在 TypeScript 开发中,很多人只使用了最基础的类型注解,却错过了 TypeScript 真正强大的功能。本文将深入探讨 5 个高级类型技巧,帮助你写出更安全、更可维护的代码。

一、条件类型:让类型智能起来

条件类型允许我们根据类型参数来决定最终类型,类似于 JavaScript 中的三元表达式。

TypeScript
// 基础语法
type IsString<T> = T extends string ? true : false;

type A = IsString<string>;  // true
type B = IsString<number>;  // false

实际应用场景:提取 Promise 返回类型

TypeScript
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type Result = UnwrapPromise<Promise<string>>;  // string
type Result2 = UnwrapPromise<number>;  // number

这个技巧在处理异步函数返回值时特别有用,可以避免手动追踪嵌套的 Promise 类型。

二、映射类型:批量转换属性

映射类型允许我们基于现有类型创建新类型,类似于数组的 INLINE_CODE_0 方法。

TypeScript
// 将所有属性变为可选
type Partial<T> = {
  [P in keyof T]?: T[P];
};

// 将所有属性变为只读
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

实战:创建 API 响应类型

TypeScript
interface User {
  id: number;
  name: string;
  email: string;
  createdAt: Date;
}

// API 返回的 User 需要序列化日期
type SerializedUser = {
  [P in keyof User]: User[P] extends Date ? string : User[P];
};

// 结果:id, name, email 保持原类型,createdAt 变为 string

三、工具类型的组合使用

TypeScript 内置了多个实用工具类型,组合使用可以解决复杂场景。

TypeScript
interface Config {
  apiUrl: string;
  timeout: number;
  retry: number;
  debug: boolean;
}

// 只保留部分字段
type ApiConfig = Pick<Config, 'apiUrl' | 'timeout'>;

// 排除某些字段
type RuntimeConfig = Omit<Config, 'apiUrl'>;

// 创建更新类型(部分字段可选)
type ConfigUpdate = Partial<Pick<Config, 'timeout' | 'debug'>>;

深度 Partial:处理嵌套对象

内置的 INLINE_CODE_1 只处理第一层,对于嵌套对象需要自定义:

TypeScript
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

interface NestedConfig {
  database: {
    host: string;
    port: number;
  };
  cache: {
    enabled: boolean;
    ttl: number;
  };
}

// 现在可以只更新深层的某个属性
type ConfigUpdate = DeepPartial<NestedConfig>;

四、类型守卫:运行时类型检查

类型守卫函数可以在运行时缩窄类型,让 TypeScript 在后续代码中知道具体类型。

TypeScript
// 自定义类型守卫
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function processInput(input: string | number) {
  if (isString(input)) {
    // 这里 input 被缩窄为 string
    return input.toUpperCase();
  }
  // 这里 input 被缩窄为 number
  return input.toFixed(2);
}

in 操作符守卫

TypeScript
interface Dog {
  bark(): void;
}

interface Cat {
  meow(): void;
}

function makeSound(animal: Dog | Cat) {
  if ('bark' in animal) {
    animal.bark();  // animal 是 Dog
  } else {
    animal.meow();  // animal 是 Cat
  }
}

五、泛型约束:限制类型参数范围

使用 INLINE_CODE_2 关键字可以约束泛型参数必须满足某些条件。

TypeScript
// 约束 T 必须有 length 属性
function logLength<T extends { length: number }>(value: T): void {
  console.log(value.length);
}

logLength('hello');  // ✓ 5
logLength([1, 2, 3]);  // ✓ 3
logLength(123);  // ✗ number 没有 length 属性

多约束场景

TypeScript
interface Identifiable {
  id: string | number;
}

interface Creatable {
  createdAt: Date;
}

// 同时约束多个接口
function processEntity<T extends Identifiable & Creatable>(entity: T) {
  console.log(`ID: ${entity.id}, Created: ${entity.createdAt}`);
}

六、实战:构建类型安全的 API 客户端

结合以上技巧,我们来构建一个类型安全的 API 客户端。

TypeScript
// 定义 API 响应结构
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

// 提取数据类型
type ResponseData<T> = T extends ApiResponse<infer U> ? U : never;

// 定义用户类型
interface User {
  id: number;
  name: string;
  email: string;
}

// API 函数返回类型
async function fetchUser(id: number): Promise<ApiResponse<User>> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

// 使用
async function getUser(id: number) {
  const result = await fetchUser(id);
  // TypeScript 知道 result.data 是 User 类型
  console.log(result.data.name);  // ✓ 类型安全
}

七、常见陷阱与解决方案

陷阱 1:any 类型滥用

TypeScript
// ❌ 错误:失去类型检查
function process(data: any) {
  return data.value;
}

// ✓ 正确:使用 unknown
function processSafe(data: unknown) {
  if (typeof data === 'object' && data !== null && 'value' in data) {
    return (data as { value: string }).value;
  }
  throw new Error('Invalid data');
}

陷阱 2:过度使用类型断言

TypeScript
// ❌ 错误:绕过类型检查
const element = document.getElementById('app') as HTMLDivElement;

// ✓ 正确:先检查
const element = document.getElementById('app');
if (element instanceof HTMLDivElement) {
  // 安全使用 element
}

总结

掌握这些高级类型技巧后,你可以:

  1. 编写更安全的代码:在编译时捕获更多错误
  2. 提高开发效率:获得更好的自动补全和类型提示
  3. 增强代码可维护性:类型即文档,新人更容易理解
  4. 减少运行时错误:类型检查覆盖更多边界情况

TypeScript 的类型系统非常强大,但需要时间和实践来掌握。建议从实际项目中的小场景开始应用,逐步深入。

延伸学习


本文首发于 railx 博客,欢迎转载但请注明出处。

分享到:

如果这篇文章对你有帮助,欢迎请作者喝杯咖啡 ☕

加载评论中...