TypeScript 高级类型

172 阅读6分钟

TypeScript 高级类型概念图

在 TypeScript 中,高级类型(Advanced Types)是一组强大的工具,能够帮助你创建更精确、灵活和可维护的类型定义。这些类型超越了基本的接口和类型别名,提供了组合、转换和操作类型的先进方法。本文将深入探讨 TypeScript 中最实用的高级类型及其实际应用场景。

1. 联合类型(Union Types)

联合类型允许一个值可以属于多种类型中的一种,使用 | 运算符表示:

type Status = 'pending' | 'success' | 'error';

function handleResponse(status: Status) {
  // 根据不同的状态值进行处理
}

// 应用示例:函数参数可以是多种类型
function formatInput(input: string | number | Date) {
  if (typeof input === 'string') {
    return input.trim();
  }
  if (input instanceof Date) {
    return input.toISOString();
  }
  return input.toFixed(2);
}

实用场景

  • 表示可能的状态值
  • 处理多种输入类型
  • 替代枚举类型的轻量级方案

2. 交叉类型(Intersection Types)

交叉类型使用 & 运算符组合多个类型,创建一个包含所有类型特性的新类型:

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

interface Employee {
  employeeId: number;
  department: string;
}

type Staff = Person & Employee;

const staffMember: Staff = {
  name: 'Alice Zhang',
  age: 32,
  employeeId: 1001,
  department: 'Engineering'
};

// 实用例子:扩展函数选项
type DataFetchOptions = { timeout: number } & RequestInit;

async function fetchData(url: string, options: DataFetchOptions) {
  return fetch(url, {
    ...options,
    timeout: options.timeout || 5000
  });
}

3. 类型别名(Type Aliases)

类型别名为你复杂的类型创建新名称,提高代码可读性:

// 基本类型别名
type ID = string | number;
type Callback<T> = (result: T) => void;

// 复杂类型别名
type Coordinate = {
  x: number;
  y: number;
  z?: number; // 可选属性
};

type Point3D = Coordinate & { z: number };

// 实用示例:简化复杂类型
type UserProfile = {
  id: ID;
  name: string;
  email: string;
  preferences: {
    theme: 'light' | 'dark';
    notifications: boolean;
  };
};

function updateProfile(profile: Partial<UserProfile>) {
  // 部分更新用户配置
}

4. 索引类型和映射类型(Index and Mapped Types)

TypeScript 提供了根据已知类型创建新类型的映射能力:

基本映射类型

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Partial<T> = {
  [P in keyof T]?: T[P];
};

type Required<T> = {
  [P in keyof T]-?: T[P];
};

// 应用示例
interface User {
  name: string;
  email?: string;
}

const readOnlyUser: Readonly<User> = {
  name: 'Bob',
  email: 'bob@example.com'
};

// readOnlyUser.name = 'Alice'; // 错误:只读属性

const partialUser: Partial<User> = {
  email: 'updated@example.com' // 只提供部分属性
};

高级映射类型:键重映射

// 添加getter方法
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

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

type PersonGetters = Getters<Person>;
// 等价于:
// {
//   getName: () => string;
//   getAge: () => number;
// }

// 过滤特定属性
type OnlyMethods<T> = {
  [K in keyof T as T[K] extends Function ? K : never]: T[K];
};

class MyService {
  id: number = 1;
  fetchData() {}
  calculate() {}
}

type ServiceMethods = OnlyMethods<MyService>; // { fetchData: () => void; calculate: () => void; }

5. 条件类型(Conditional Types)

条件类型基于条件表达式选择不同的类型,语法为 T extends U ? X : Y

// 基础条件类型
type IsString<T> = T extends string ? true : false;

type A = IsString<'hello'>; // true
type B = IsString<123>;     // false

// 实用例子:从数组中提取元素类型
type Flatten<T> = T extends Array<infer U> ? U : T;

type Numbers = Flatten<number[]>; // number
type Mixed = Flatten<(string | number)[]>; // string | number

// 嵌套条件类型:检查元组类型
type IsTuple<T> = 
  T extends readonly any[] ?
    number extends T['length'] ? false : true
  : false;

type T1 = IsTuple<[number, string]>; // true
type T2 = IsTuple<number[]>;         // false

6. 类型推断与 infer 关键字

infer 关键字在条件类型中用于从比较类型中提取类型:

// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function getUser() {
  return { name: 'Alice', age: 30 };
}

type User = ReturnType<typeof getUser>; // { name: string; age: number }

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

type Colors = ArrayElement<string[]>; // string

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

type ResolvedValue = UnwrapPromise<Promise<number>>; // number

7. 模板文本类型(Template Literal Types)

模板文本类型允许基于字符串模板创建类型:

type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiEndpoint = `/api/${string}`;

type ApiRoute = `${HttpMethod} ${ApiEndpoint}`;

function handleRequest(route: ApiRoute) {
  // 处理路由
}

handleRequest('GET /api/users'); // 正确
handleRequest('POST /api/products'); // 正确
handleRequest('GET /users'); // 错误:不以/api开头

// 高级用法:路径参数提取
type ExtractParam<Path extends string> = 
  Path extends `${string}/:${infer Param}/${infer Rest}`
    ? [Param, ...ExtractParam<`/${Rest}`>]
    : Path extends `${string}/:${infer Param}`
      ? [Param]
      : [];

type Params = ExtractParam<'/users/:userId/posts/:postId'>; 
// ['userId', 'postId']

8. 实用类型(Utility Types)

TypeScript 提供了一套内置的实用类型:

常用实用类型

// Partial:所有属性变为可选
type PartialUser = Partial<User>;

// Readonly:所有属性变为只读
type ReadonlyUser = Readonly<User>;

// Pick:选择特定属性
type UserName = Pick<User, 'name'>;

// Omit:排除特定属性
type UserWithoutEmail = Omit<User, 'email'>;

// Record:创建属性映射
type PageViews = Record<'home' | 'about' | 'contact', number>;

// Exclude:从联合类型中排除某些类型
type Letters = 'a' | 'b' | 'c';
type WithoutC = Exclude<Letters, 'c'>; // 'a' | 'b'

// Extract:从联合类型中提取可赋给特定类型的类型
type Numericals = Extract<string | number | boolean, number | string>; // string | number

自定义实用类型

// 深度只读
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

// 深度可选
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

// 不可为空类型
type NonNullableProperties<T> = {
  [K in keyof T]: NonNullable<T[K]>;
};

// 函数参数类型
type ParametersExceptFirst<T> = 
  T extends (first: any, ...rest: infer U) => any ? U : never;

function greet(greeting: string, name: string, punctuation: string) {
  return `${greeting}, ${name}${punctuation}`;
}

type GreetParams = ParametersExceptFirst<typeof greet>; // [string, string]

9. 递归类型(Recursive Types)

递归类型允许类型定义引用自身,非常适合定义树形结构:

// JSON 值类型(递归定义)
type JSONValue = 
  | string 
  | number 
  | boolean 
  | null 
  | JSONValue[] 
  | { [key: string]: JSONValue };

// 文件系统结构
type File = {
  name: string;
  size: number;
};

type Directory = {
  name: string;
  children: Array<File | Directory>;
};

type FileSystem = Directory;

// 链表结构
type ListNode<T> = {
  value: T;
  next: ListNode<T> | null;
};

const list: ListNode<number> = {
  value: 1,
  next: {
    value: 2,
    next: {
      value: 3,
      next: null
    }
  }
};

10. 声明合并(Declaration Merging)

声明合并允许将多个同名的接口声明合并为一个:

// 接口合并
interface User {
  name: string;
}

interface User {
  age: number;
}

const user: User = {
  name: 'Alice',
  age: 30
};

// 命名空间合并
namespace MathUtils {
  export function add(a: number, b: number) {
    return a + b;
  }
}

namespace MathUtils {
  export function subtract(a: number, b: number) {
    return a - b;
  }
}

console.log(MathUtils.add(5, 3)); // 8
console.log(MathUtils.subtract(5, 3)); // 2

实际应用场景

1. 实现类型安全的 Redux reducer

type Action<T extends string, P> = {
  type: T;
  payload: P;
};

function createAction<T extends string, P>(type: T, payload: P): Action<T, P> {
  return { type, payload };
}

// 定义动作
const loginAction = createAction('USER_LOGIN', { username: 'alice', token: 'abc123' });
const logoutAction = createAction('USER_LOGOUT', undefined);

// 动作类型
type Actions = ReturnType<typeof loginAction> | ReturnType<typeof logoutAction>;

// Reducer
function userReducer(
  state: UserState | null, 
  action: Actions
): UserState | null {
  switch (action.type) {
    case 'USER_LOGIN':
      return action.payload; // 自动推断为登录载荷类型
    case 'USER_LOGOUT':
      return null;
    default:
      return state;
  }
}

2. API 响应类型转换

// API 响应结构
type ApiResponse<T> = {
  status: 'success' | 'error';
  timestamp: string;
  data: T;
  error?: string;
};

// 用户API
type UserApiResponse = ApiResponse<{
  id: number;
  name: string;
  email: string;
}>;

// 提取数据类型的实用类型
type ApiData<T> = T extends ApiResponse<infer D> ? D : never;

// 处理API响应
async function fetchData<T>(url: string): Promise<ApiData<T>> {
  const response = await fetch(url);
  const result: T = await response.json();
  
  if (result.status === 'error') {
    throw new Error(result.error || 'Unknown error');
  }
  
  return result.data; // 返回提取的数据部分
}

// 使用
const userData = await fetchData<UserApiResponse>('/api/user');
console.log(userData.name); // 类型安全访问

高级类型的最佳实践

  1. 渐进式类型化

    // 从简单类型开始,逐步增加复杂性
    type PaymentMethod = 'credit_card' | 'paypal';
    
    // 添加更多细节
    type PaymentDetails = 
      | { method: 'credit_card'; cardNumber: string; expiry: string }
      | { method: 'paypal'; email: string };
    
    // 最终整合
    type Payment = {
      amount: number;
      currency: string;
    } & PaymentDetails;
    
  2. 优先使用类型别名

    // 更清晰地表达复杂类型关系
    type Coordinates = [number, number];
    type Address = {
      street: string;
      city: string;
      coords: Coordinates;
    };
    
  3. 利用类型推导减少冗余

    const config = {
      apiUrl: 'https://api.example.com',
      timeout: 5000,
      maxRetries: 3
    } as const; // 使用const断言
    
    type Config = typeof config;
    // 自动推导为:{ readonly apiUrl: "https://api.example.com"; readonly timeout: 5000; readonly maxRetries: 3; }
    
  4. 组合而非继承

    // 使用组合而不是类继承
    type Entity = {
      id: string;
      createdAt: Date;
    };
    
    type User = Entity & {
      name: string;
      email: string;
    };
    
    type Product = Entity & {
      name: string;
      price: number;
    };
    

常见错误和解决方案

错误1:过度复杂的类型

症状:类型定义嵌套过深,难以理解

解决方案

// 分解复杂类型
type UserAuthentication = {
  username: string;
  passwordHash: string;
  lastLogin: Date;
};

type UserProfile = {
  displayName: string;
  avatarUrl: string;
};

type User = UserAuthentication & UserProfile;

错误2:类型别名滥用

症状:为简单类型创建不必要的别名

解决方案:只在提高可读性或减少重复时使用类型别名

错误3:忽略性能影响

症状:复杂的递归类型导致类型检查缓慢

解决方案

// 使用接口限制递归深度
type JsonValue = 
  | string 
  | number 
  | boolean 
  | null 
  | { [key: string]: JsonValue } 
  | JsonValue[];

TypeScript 高级类型的未来

随着 TypeScript 的持续发展,更多高级功能正在引入:

  1. satisfies 运算符(TypeScript 4.9+)

    const colors = ['red', 'green', 'blue'] satisfies string[];
    const firstColor = colors[0]; // 正确推断为 string
    
  2. 装饰器元数据(TypeScript 5.0+)

    @Reflect.metadata('design:type', Function)
    class MyDecorator {
      // ...
    }
    
  3. 扩展模式匹配能力

    // 未来可能支持(提案阶段)
    type First<T> = T extends [infer First, ...infer _] ? First : never;
    

小结

TypeScript 的高级类型系统极大地增强了语言的表达能力,提供了以下优势:

  1. 类型安全:在编译时捕获更多错误
  2. 代码可维护性:明确的类型定义作为文档
  3. 开发效率:智能工具提示和自动完成
  4. 架构清晰度:表达复杂的数据结构和模式
graph TD
    A[基础类型]
    B[高级类型]
    A -->|扩展为| B
    
    B --> C[组合类型]
    B --> D[条件类型]
    B --> E[映射类型]
    B --> F[实用类型]
    B --> G[模板文本类型]
    B --> H[递归类型]
    
    C --> I[联合类型]
    C --> J[交叉类型]
    D --> K[类型推断]
    D --> L[类型守卫]
    E --> M[键映射]
    E --> N[键过滤]
    F --> O[内置实用类型]
    F --> P[自定义类型工具]

通过掌握这些高级类型,你可以创建更具表现力的类型定义,构建更健壮的应用,并在团队协作中提供清晰的接口契约。TypeScript 的类型系统就像一张安全网,而高级类型使它更加坚固。