阿里云主机折上折
  • 微信号
Current Site:Index > Complex generic type resolution

Complex generic type resolution

Author:Chuan Chen 阅读数:62765人阅读 分类: TypeScript

Complex Generic Type Resolution

TypeScript's generic system allows developers to create reusable components while maintaining type safety. When generic types begin to nest and combine, type resolution becomes complex yet powerful. Understanding the resolution mechanisms of these complex generic types helps developers build more flexible type systems.

Review of Basic Generics

The basic form of generics uses type parameters:

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

When this function is called, TypeScript automatically infers the type parameter:

let output = identity<string>("hello");  // Explicitly specified
let output2 = identity("hello");         // Automatically inferred as string

Generic Constraints

Generic parameters can be constrained using the extends keyword:

interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

Conditional Types

Conditional types are a key feature in generic type resolution:

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

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

Distributive Conditional Types

When conditional types act on union types, distributive resolution occurs:

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

type StrOrNumArray = ToArray<string | number>;  
// Resolves to string[] | number[]

Type Inference with infer

The infer keyword can declare type variables within conditional types:

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

type FnReturn = ReturnType<() => number>;  // number

Mapped Types

Mapped types can create new types based on existing ones:

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

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

type ReadonlyPerson = Readonly<Person>;

Recursive Types

TypeScript supports recursive type definitions:

type JsonValue = 
  | string 
  | number 
  | boolean 
  | null 
  | JsonValue[] 
  | { [key: string]: JsonValue };

const json: JsonValue = {
  name: "John",
  age: 30,
  hobbies: ["reading", { type: "sports", frequency: "daily" }]
};

Template Literal Types

Combined with generics, powerful string types can be created:

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

type Concat<A extends string, B extends string> = `${A}${B}`;

type T0 = EventName<'foo'>;  // 'fooChanged'
type T1 = Concat<'Hello', 'World'>;  // 'HelloWorld'

Parsing Complex Generic Examples

Parsing a complex utility type:

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

interface User {
  id: number;
  info: {
    name: string;
    age: number;
    address: {
      city: string;
      country: string;
    };
  };
}

type PartialUser = DeepPartial<User>;
/* Equivalent to:
{
  id?: number;
  info?: {
    name?: string;
    age?: number;
    address?: {
      city?: string;
      country?: string;
    };
  };
}
*/

Type Programming Techniques

Combining multiple advanced features to create utility types:

type GetProps<T> = 
  T extends React.ComponentType<infer P> ? P : never;

type ExtractState<T> = 
  T extends React.Component<infer P, infer S> ? S : never;

type PromiseValue<T> = 
  T extends Promise<infer V> ? V : T;

Type Compatibility Checks

Using conditional types to check type compatibility:

type IsAssignable<T, U> = T extends U ? true : false;

type T1 = IsAssignable<number, string | number>;  // true
type T2 = IsAssignable<string, number>;          // false

Advanced Generic Utility Types

Creating more complex utility types:

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

type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;

type T3 = Flatten<number[][]>;  // number

Type-Level Programming Challenges

Solving practical type programming problems:

type Join<T extends string[], D extends string> =
  T extends [] ? '' :
  T extends [infer F] ? F :
  T extends [infer F, ...infer R] ?
    F extends string ?
      R extends string[] ?
        `${F}${D}${Join<R, D>}` : never : never : never;

type T4 = Join<['a', 'b', 'c'], '-'>;  // 'a-b-c'

Exploring the Boundaries of the Type System

Pushing the limits of TypeScript's type system:

type Fibonacci<T extends number, N1 extends number = 1, N2 extends number = 1> =
  T extends 0 ? 0 :
  T extends 1 | 2 ? 1 :
  T extends number ? Fibonacci<Subtract<T, 1>, N2, Add<N1, N2>> : never;

// Requires defining Add and Subtract types
type Add<A extends number, B extends number> = [...Array<A>, ...Array<B>]['length'];
type Subtract<A extends number, B extends number> = 
  Array<A> extends [...Array<B>, ...infer R] ? R['length'] : never;

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:泛型性能考量

下一篇:泛型设计模式

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.