mcpField
T
typescript-advanced-types

typescript-advanced-types

github.com/rolling-scopes开发10.4k36 次安装6 天前更新
代码仓库

关于 typescript-advanced-types

面向 TypeScript 开发者的进阶类型系统指南,涵盖类型推断、可复用类型模式与测试,适用于构建类型安全的应用。

主要功能

  1. 01泛型与条件类型
  2. 02映射类型与模板字面量类型
  3. 03工具类型与递归类型
  4. 04类型推断与类型守卫
  5. 05类型测试与实践建议

适用场景

  1. 01构建类型安全的 API 客户端
  2. 02创建可复用的泛型组件
  3. 03设计表单验证类型
  4. 04将 JavaScript 代码迁移到 TypeScript

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rolling-scopes/rsschool-app --skill typescript-advanced-types

安装后在 Claude Code 中输入/typescript-advanced-types确认已加载。

详细介绍

TypeScript 高级类型

全面介绍如何精通 TypeScript 的高级类型系统,包括泛型、条件类型、映射类型、模板字面量类型和工具类型,助你构建健壮且类型安全的应用。

何时使用此技能

  • 构建类型安全的库或框架
  • 创建可复用的泛型组件
  • 实现复杂的类型推断逻辑
  • 设计类型安全的 API 客户端
  • 构建表单验证系统
  • 创建强类型配置对象
  • 实现类型安全的状态管理
  • 将 JavaScript 代码库迁移到 TypeScript

核心概念

1. 泛型

用途: 在保持类型安全的同时,创建可复用且类型灵活的组件。

基本泛型函数:

function identity<T>(value: T): T {
  return value;
}

const num = identity<number>(42); // Type: number
const str = identity<string>('hello'); // Type: string
const auto = identity(true); // Type inferred: boolean

泛型约束:

interface HasLength {
  length: number;
}

function logLength<T extends HasLength>(item: T): T {
  console.log(item.length);
  return item;
}

logLength('hello'); // OK: string has length
logLength([1, 2, 3]); // OK: array has length
logLength({ length: 10 }); // OK: object has length
// logLength(42);             // Error: number has no length

多个类型参数:

function merge<T, U>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

const merged = merge({ name: 'John' }, { age: 30 });
// Type: { name: string } & { age: number }
2. 条件类型

用途: 创建取决于条件的类型,以实现复杂的类型逻辑。

基本条件类型:

type IsString<T> = T extends string ? true : false;

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

提取返回类型:

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function getUser() {
  return { id: 1, name: 'John' };
}

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

分布式条件类型:

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

type StrOrNumArray = ToArray<string | number>;
// Type: string[] | number[]

嵌套条件:

type TypeName<T> = T extends string
  ? 'string'
  : T extends number
    ? 'number'
    : T extends boolean
      ? 'boolean'
      : T extends undefined
        ? 'undefined'
        : T extends Function
          ? 'function'
          : 'object';

type T1 = TypeName<string>; // "string"
type T2 = TypeName<() => void>; // "function"
3. 映射类型

用途: 遍历现有类型的属性并对其进行转换。

基本映射类型:

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

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

type ReadonlyUser = Readonly<User>;
// Type: { readonly id: number; readonly name: string; }

可选属性:

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

type PartialUser = Partial<User>;
// Type: { id?: number; name?: string; }

键重映射:

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

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

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

筛选属性:

type PickByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K];
};

interface Mixed {
  id: number;
  name: string;
  age: number;
  active: boolean;
}

type OnlyNumbers = PickByType<Mixed, number>;
// Type: { id: number; age: number; }
4. 模板字面量类型

用途: 创建基于字符串的类型,并支持模式匹配和转换。

基本模板字面量:

type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize<EventName>}`;
// Type: "onClick" | "onFocus" | "onBlur"

字符串操作:

type UppercaseGreeting = Uppercase<'hello'>; // "HELLO"
type LowercaseGreeting = Lowercase<'HELLO'>; // "hello"
type CapitalizedName = Capitalize<'john'>; // "John"
type UncapitalizedName = Uncapitalize<'John'>; // "john"

路径构建:

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

interface Config {
  server: {
    host: string;
    port: number;
  };
  database: {
    url: string;
  };
}

type ConfigPath = Path<Config>;
// Type: "server" | "database" | "server.host" | "server.port" | "database.url"
5. 工具类型

内置工具类型:

// Partial<T> - Make all properties optional
type PartialUser = Partial<User>;

// Required<T> - Make all properties required
type RequiredUser = Required<PartialUser>;

// Readonly<T> - Make all properties readonly
type ReadonlyUser = Readonly<User>;

// Pick<T, K> - Select specific properties
type UserName = Pick<User, 'name' | 'email'>;

// Omit<T, K> - Remove specific properties
type UserWithoutPassword = Omit<User, 'password'>;

// Exclude<T, U> - Exclude types from union
type T1 = Exclude<'a' | 'b' | 'c', 'a'>; // "b" | "c"

// Extract<T, U> - Extract types from union
type T2 = Extract<'a' | 'b' | 'c', 'a' | 'b'>; // "a" | "b"

// NonNullable<T> - Exclude null and undefined
type T3 = NonNullable<string | null | undefined>; // string

// Record<K, T> - Create object type with keys K and values T
type PageInfo = Record<'home' | 'about', { title: string }>;

高级模式

模式 1:类型安全的事件发射器
type EventMap = {
  'user:created': { id: string; name: string };
  'user:updated': { id: string };
  'user:deleted': { id: string };
};

class TypedEventEmitter<T extends Record<string, any>> {
  private listeners: {
    [K in keyof T]?: Array<(data: T[K]) => void>;
  } = {};

  on<K extends keyof T>(event: K, callback: (data: T[K]) => void): void {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event]!.push(callback);
  }

  emit<K extends keyof T>(event: K, data: T[K]): void {
    const callbacks = this.listeners[event];
    if (callbacks) {
      callbacks.forEach(callback => callback(data));
    }
  }
}

const emitter = new TypedEventEmitter<EventMap>();

emitter.on('user:created', data => {
  console.log(data.id, data.name); // Type-safe!
});

emitter.emit('user:created', { id: '1', name: 'John' });
// emitter.emit("user:created", { id: "1" });  // Error: missing 'name'
模式 2:类型安全的 API 客户端
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';

type EndpointConfig = {
  '/users': {
    GET: { response: User[] };
    POST: { body: { name: string; email: string }; response: User };
  };
  '/users/:id': {
    GET: { params: { id: string }; response: User };
    PUT: { params: { id: string }; body: Partial<User>; response: User };
    DELETE: { params: { id: string }; response: void };
  };
};

type ExtractParams<T> = T extends { params: infer P } ? P : never;
type ExtractBody<T> = T extends { body: infer B } ? B : never;
type ExtractResponse<T> = T extends { response: infer R } ? R : never;

class APIClient<Config extends Record<string, Record<HTTPMethod, any>>> {
  async request<Path extends keyof Config, Method extends keyof Config[Path]>(
    path: Path,
    method: Method,
    ...[options]: ExtractParams<Config[Path][Method]> extends never
      ? ExtractBody<Config[Path][Method]> extends never
        ? []
        : [{ body: ExtractBody<Config[Path][Method]> }]
      : [
          {
            params: ExtractParams<Config[Path][Method]>;
            body?: ExtractBody<Config[Path][Method]>;
          },
        ]
  ): Promise<ExtractResponse<Config[Path][Method]>> {
    // Implementation here
    return {} as any;
  }
}

const api = new APIClient<EndpointConfig>();

// Type-safe API calls
const users = await api.request('/users', 'GET');
// Type: User[]

const newUser = await api.request('/users', 'POST', {
  body: { name: 'John', email: '[email protected]' },
});
// Type: User

const user = await api.request('/users/:id', 'GET', {
  params: { id: '123' },
});
// Type: User
模式 3:具有类型安全性的构建器模式
type BuilderState<T> = {
  [K in keyof T]: T[K] | undefined;
};

type RequiredKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];

type OptionalKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];

type IsComplete<T, S> = RequiredKeys<T> extends keyof S ? (S[RequiredKeys<T>] extends undefined ? false : true) : false;

class Builder<T, S extends BuilderState<T> = {}> {
  private state: S = {} as S;

  set<K extends keyof T>(key: K, value: T[K]): Builder<T, S & Record<K, T[K]>> {
    this.state[key] = value;
    return this as any;
  }

  build(this: IsComplete<T, S> extends true ? this : never): T {
    return this.state as T;
  }
}

interface User {
  id: string;
  name: string;
  email: string;
  age?: number;
}

const builder = new Builder<User>();

const user = builder.set('id', '1').set('name', 'John').set('email', '[email protected]').build(); // OK: all required fields set

// const incomplete = builder
//   .set("id", "1")
//   .build();  // Error: missing required fields
模式 4:深层只读/部分类型
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? (T[P] extends Function ? T[P] : DeepReadonly<T[P]>) : T[P];
};

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

interface Config {
  server: {
    host: string;
    port: number;
    ssl: {
      enabled: boolean;
      cert: string;
    };
  };
  database: {
    url: string;
    pool: {
      min: number;
      max: number;
    };
  };
}

type ReadonlyConfig = DeepReadonly<Config>;
// All nested properties are readonly

type PartialConfig = DeepPartial<Config>;
// All nested properties are optional
模式 5:类型安全的表单验证
type ValidationRule<T> = {
  validate: (value: T) => boolean;
  message: string;
};

type FieldValidation<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 rules: FieldValidation<T>) {}

  validate(data: T): ValidationErrors<T> | null {
    const errors: ValidationErrors<T> = {};
    let hasErrors = false;

    for (const key in this.rules) {
      const fieldRules = this.rules[key];
      const value = data[key];

      if (fieldRules) {
        const fieldErrors: string[] = [];

        for (const rule of fieldRules) {
          if (!rule.validate(value)) {
            fieldErrors.push(rule.message);
          }
        }

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

    return hasErrors ? errors : null;
  }
}

interface LoginForm {
  email: string;
  password: string;
}

const validator = new FormValidator<LoginForm>({
  email: [
    {
      validate: v => v.includes('@'),
      message: 'Email must contain @',
    },
    {
      validate: v => v.length > 0,
      message: 'Email is required',
    },
  ],
  password: [
    {
      validate: v => v.length >= 8,
      message: 'Password must be at least 8 characters',
    },
  ],
});

const errors = validator.validate({
  email: 'invalid',
  password: 'short',
});
// Type: { email?: string[]; password?: string[]; } | null
模式 6:可辨识联合
type Success<T> = {
  status: 'success';
  data: T;
};

type Error = {
  status: 'error';
  error: string;
};

type Loading = {
  status: 'loading';
};

type AsyncState<T> = Success<T> | Error | Loading;

function handleState<T>(state: AsyncState<T>): void {
  switch (state.status) {
    case 'success':
      console.log(state.data); // Type: T
      break;
    case 'error':
      console.log(state.error); // Type: string
      break;
    case 'loading':
      console.log('Loading...');
      break;
  }
}

// Type-safe state machine
type State =
  | { type: 'idle' }
  | { type: 'fetching'; requestId: string }
  | { type: 'success'; data: any }
  | { type: 'error'; error: Error };

type Event =
  | { type: 'FETCH'; requestId: string }
  | { type: 'SUCCESS'; data: any }
  | { type: 'ERROR'; error: Error }
  | { type: 'RESET' };

function reducer(state: State, event: Event): State {
  switch (state.type) {
    case 'idle':
      return event.type === 'FETCH' ? { type: 'fetching', requestId: event.requestId } : state;
    case 'fetching':
      if (event.type === 'SUCCESS') {
        return { type: 'success', data: event.data };
      }
      if (event.type === 'ERROR') {
        return { type: 'error', error: event.error };
      }
      return state;
    case 'success':
    case 'error':
      return event.type === 'RESET' ? { type: 'idle' } : state;
  }
}

类型推断技巧

1. Infer 关键字
// Extract array element type
type ElementType<T> = T extends (infer U)[] ? U : never;

type NumArray = number[];
type Num = ElementType<NumArray>; // number

// Extract promise type
type PromiseType<T> = T extends Promise<infer U> ? U : never;

type AsyncNum = PromiseType<Promise<number>>; // number

// Extract function parameters
type Parameters<T> = T extends (...args: infer P) => any ? P : never;

function foo(a: string, b: number) {}
type FooParams = Parameters<typeof foo>; // [string, number]
2. 类型守卫
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function isArrayOf<T>(value: unknown, guard: (item: unknown) => item is T): value is T[] {
  return Array.isArray(value) && value.every(guard);
}

const data: unknown = ['a', 'b', 'c'];

if (isArrayOf(data, isString)) {
  data.forEach(s => s.toUpperCase()); // Type: string[]
}
3. 断言函数
function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== 'string') {
    throw new Error('Not a string');
  }
}

function processValue(value: unknown) {
  assertIsString(value);
  // value is now typed as string
  console.log(value.toUpperCase());
}

最佳实践

  1. 优先使用 unknown,而非 any:强制进行类型检查
  2. 对象结构优先使用 interface:错误消息更清晰
  3. 联合类型和复杂类型使用 type:灵活性更高
  4. 充分利用类型推断:尽可能让 TypeScript 自动推断
  5. 创建辅助类型:构建可复用的类型工具
  6. 使用 const 断言:保留字面量类型
  7. 避免类型断言:改用类型守卫
  8. 为复杂类型添加文档:添加 JSDoc 注释
  9. 使用严格模式:启用所有严格编译器选项
  10. 测试类型:使用类型测试验证类型行为

类型测试

// Type assertion tests
type AssertEqual<T, U> = [T] extends [U] ? ([U] extends [T] ? true : false) : false;

type Test1 = AssertEqual<string, string>; // true
type Test2 = AssertEqual<string, number>; // false
type Test3 = AssertEqual<string | number, string>; // false

// Expect error helper
type ExpectError<T extends never> = T;

// Example usage
type ShouldError = ExpectError<AssertEqual<string, number>>;

常见陷阱

  1. 过度使用 any:违背 TypeScript 的初衷
  2. 忽略严格空值检查:可能导致运行时错误
  3. 类型过于复杂:可能拖慢编译速度
  4. 未使用可辨识联合:错失类型收窄的机会
  5. 忘记 readonly 修饰符:可能导致意外修改
  6. 循环类型引用:可能引发编译器错误
  7. 未处理边缘情况:例如空数组或 null 值

性能注意事项

  • 避免深层嵌套的条件类型
  • 尽可能使用简单类型
  • 缓存复杂类型计算
  • 限制递归类型的递归深度
  • 使用构建工具在生产环境中跳过类型检查

资源

评论

支持 Markdown