前端开发8.775 分钟阅读

TypeScript 高级类型实战指南

深入探索 TypeScript 的高级类型系统,包括条件类型、映射类型、模板字面量类型等,通过实际案例学习如何构建类型安全的应用。

作者:li-lingfeng发布于 2024年1月28日

TypeScript 高级类型实战指南

TypeScript 的类型系统非常强大,掌握高级类型技巧可以让我们写出更安全、更优雅的代码。本文将深入探讨 TypeScript 的高级类型特性。

条件类型 (Conditional Types)

条件类型允许我们根据条件选择类型,语法类似于三元运算符。

基础语法

type ConditionalType<T> = T extends string ? string : number;

type Test1 = ConditionalType<string>; // string
type Test2 = ConditionalType<number>; // number

实际应用:类型守卫

type NonNullable<T> = T extends null | undefined ? never : T;

type Example1 = NonNullable<string | null>; // string
type Example2 = NonNullable<number | undefined>; // number

分布式条件类型

当条件类型作用于联合类型时,会分布到每个成员:

type ToArray<T> = T extends any ? T[] : never;

type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]

映射类型 (Mapped Types)

映射类型可以基于现有类型创建新类型。

内置映射类型

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

// Required - 所有属性变为必需
type Required<T> = {
  [P in keyof T]-?: T[P];
};

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

自定义映射类型

// 为所有属性添加前缀
type Prefixed<T, P extends string> = {
  [K in keyof T as `${P}${string & K}`]: T[K];
};

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

type PrefixedUser = Prefixed<User, 'user_'>;
// { user_name: string; user_age: number; }

模板字面量类型 (Template Literal Types)

TypeScript 4.1 引入了模板字面量类型,允许我们在类型层面操作字符串。

基础用法

type World = "world";
type Greeting = `hello ${World}`; // "hello world"

实际应用:事件系统

type EventName<T extends string> = `on${Capitalize<T>}`;

type ButtonEvents = EventName<'click' | 'hover' | 'focus'>;
// 'onClick' | 'onHover' | 'onFocus'

// 事件处理器类型
type EventHandlers<T extends Record<string, any>> = {
  [K in keyof T as EventName<string & K>]?: (event: T[K]) => void;
};

interface Events {
  click: MouseEvent;
  hover: MouseEvent;
  focus: FocusEvent;
}

type Handlers = EventHandlers<Events>;
// {
//   onClick?: (event: MouseEvent) => void;
//   onHover?: (event: MouseEvent) => void;
//   onFocus?: (event: FocusEvent) => void;
// }

工具类型组合

深度只读类型

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

interface NestedObject {
  user: {
    profile: {
      name: string;
      settings: {
        theme: string;
      };
    };
  };
}

type ReadonlyNested = DeepReadonly<NestedObject>;
// 所有嵌套属性都变为只读

类型安全的路径访问

type PathKeys<T> = {
  [K in keyof T]: T[K] extends object
    ? K | `${string & K}.${PathKeys<T[K]>}`
    : K;
}[keyof T];

type GetByPath<T, P extends string> = P extends `${infer K}.${infer Rest}`
  ? K extends keyof T
    ? GetByPath<T[K], Rest>
    : never
  : P extends keyof T
  ? T[P]
  : never;

interface Data {
  user: {
    profile: {
      name: string;
      age: number;
    };
    settings: {
      theme: 'light' | 'dark';
    };
  };
}

type ValidPaths = PathKeys<Data>;
// 'user' | 'user.profile' | 'user.profile.name' | 'user.profile.age' | 'user.settings' | 'user.settings.theme'

function getValue<T, P extends PathKeys<T>>(obj: T, path: P): GetByPath<T, P> {
  // 实现省略
  return null as any;
}

const data: Data = { /* ... */ };
const name = getValue(data, 'user.profile.name'); // string
const theme = getValue(data, 'user.settings.theme'); // 'light' | 'dark'

实际应用案例

类型安全的 API 客户端

// API 端点定义
interface ApiEndpoints {
  '/users': {
    GET: { response: User[] };
    POST: { body: CreateUserRequest; response: User };
  };
  '/users/:id': {
    GET: { params: { id: string }; response: User };
    PUT: { params: { id: string }; body: UpdateUserRequest; response: User };
    DELETE: { params: { id: string }; response: void };
  };
}

// 提取方法类型
type Methods<T> = T extends Record<string, infer M> ? keyof M : never;

// 提取请求参数类型
type RequestParams<
  T extends Record<string, any>,
  P extends keyof T,
  M extends keyof T[P]
> = T[P][M] extends { params: infer Params } ? Params : {};

// 提取请求体类型
type RequestBody<
  T extends Record<string, any>,
  P extends keyof T,
  M extends keyof T[P]
> = T[P][M] extends { body: infer Body } ? Body : never;

// 提取响应类型
type ResponseType<
  T extends Record<string, any>,
  P extends keyof T,
  M extends keyof T[P]
> = T[P][M] extends { response: infer Response } ? Response : never;

// API 客户端类型
class ApiClient<T extends Record<string, any>> {
  async request<
    P extends keyof T,
    M extends Methods<T[P]>
  >(
    path: P,
    method: M,
    ...args: RequestBody<T, P, M> extends never
      ? [params?: RequestParams<T, P, M>]
      : [params: RequestParams<T, P, M>, body: RequestBody<T, P, M>]
  ): Promise<ResponseType<T, P, M>> {
    // 实现省略
    return null as any;
  }
}

// 使用示例
const api = new ApiClient<ApiEndpoints>();

// 类型安全的 API 调用
const users = await api.request('/users', 'GET'); // User[]
const user = await api.request('/users/:id', 'GET', { id: '123' }); // User
const newUser = await api.request('/users', 'POST', {}, { name: 'John' }); // User

表单验证类型

// 验证规则类型
type ValidationRule<T> = {
  required?: boolean;
  min?: T extends string ? number : T extends number ? number : never;
  max?: T extends string ? number : T extends number ? number : never;
  pattern?: T extends string ? RegExp : never;
  custom?: (value: T) => boolean | string;
};

// 表单模式类型
type FormSchema<T> = {
  [K in keyof T]: ValidationRule<T[K]>;
};

// 验证错误类型
type ValidationErrors<T> = {
  [K in keyof T]?: string[];
};

// 表单验证器
class FormValidator<T extends Record<string, any>> {
  constructor(private schema: FormSchema<T>) {}

  validate(data: T): ValidationErrors<T> {
    const errors: ValidationErrors<T> = {};

    for (const key in this.schema) {
      const rule = this.schema[key];
      const value = data[key];
      const fieldErrors: string[] = [];

      if (rule.required && !value) {
        fieldErrors.push('This field is required');
      }

      if (value && rule.min !== undefined) {
        if (typeof value === 'string' && value.length < rule.min) {
          fieldErrors.push(`Minimum length is ${rule.min}`);
        }
        if (typeof value === 'number' && value < rule.min) {
          fieldErrors.push(`Minimum value is ${rule.min}`);
        }
      }

      // 更多验证逻辑...

      if (fieldErrors.length > 0) {
        errors[key] = fieldErrors;
      }
    }

    return errors;
  }
}

// 使用示例
interface UserForm {
  name: string;
  email: string;
  age: number;
}

const validator = new FormValidator<UserForm>({
  name: { required: true, min: 2 },
  email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
  age: { required: true, min: 18, max: 120 }
});

const errors = validator.validate({
  name: 'John',
  email: 'john@example.com',
  age: 25
});

性能考虑

避免过度复杂的类型

// ❌ 过度复杂,可能导致编译性能问题
type OverlyComplex<T> = T extends infer U
  ? U extends Record<string, any>
    ? {
        [K in keyof U]: U[K] extends infer V
          ? V extends Record<string, any>
            ? OverlyComplex<V>
            : V
          : never;
      }
    : U
  : never;

// ✅ 简化版本
type Simplified<T> = T extends Record<string, any>
  ? { [K in keyof T]: Simplified<T[K]> }
  : T;

使用类型断言优化

// 在确保类型安全的前提下使用断言
function processData<T>(data: unknown): T {
  // 运行时验证
  if (isValidData(data)) {
    return data as T;
  }
  throw new Error('Invalid data');
}

最佳实践

  1. 渐进式采用: 从简单类型开始,逐步引入高级特性
  2. 文档化复杂类型: 为复杂的类型添加注释说明
  3. 测试类型: 使用类型测试确保类型行为正确
  4. 性能监控: 关注编译时间,避免过度复杂的类型
// 类型测试示例
type Expect<T extends true> = T;
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;

// 测试用例
type Test1 = Expect<Equal<ConditionalType<string>, string>>;
type Test2 = Expect<Equal<ConditionalType<number>, number>>;

总结

TypeScript 的高级类型系统为我们提供了强大的工具来构建类型安全的应用:

  • 条件类型: 根据条件选择类型
  • 映射类型: 转换现有类型
  • 模板字面量类型: 类型层面的字符串操作
  • 工具类型组合: 构建复杂的类型逻辑

掌握这些高级特性,能让我们的 TypeScript 代码更加健壮和可维护。记住,类型系统的目标是帮助我们写出更好的代码,而不是增加复杂性。在实际项目中,要根据需求合理使用这些特性。

相关文章

后端开发10.925 分钟

用 JavaScript 的视角学习 Rust 编程

从 JavaScript 开发者的角度深入学习 Rust 编程语言,通过对比和实例帮助前端开发者快速掌握 Rust 的核心概念。

前端开发6.71 分钟

Next.js App Router 完全指南

全面介绍 Next.js 14 的 App Router,包括路由系统、布局、加载状态、错误处理等核心概念和最佳实践。

前端开发5.1 分钟

React 18 并发特性深度解析

深入探讨 React 18 的并发渲染机制,包括 useTransition、useDeferredValue 等新 Hook 的使用场景和最佳实践。

觉得这篇文章有用?

分享给更多朋友,让知识传播得更远 ✨

评论讨论

参与讨论

登录后即可发表评论,与其他读者交流想法

加载评论中...