Complex generic type resolution
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