Conditional Types Flashcards
Q: What are conditional types in TypeScript and what is their basic syntax?
A: Conditional types allow you to create types that work like if statements, selecting different types based on a condition. They use the syntax: SomeType extends OtherType ? TrueType : FalseType.
interface Animal { live(): void } interface Dog extends Animal { woof(): void } type CheckAnimal<T> = T extends Animal ? 'yes' : 'no'; type IsDog = CheckAnimal<Dog>; // type is 'yes' type IsRegExp = CheckAnimal<RegExp>; // type is 'no'
Q: How do conditional types work with generics and how can they simplify overloads?
A: Conditional types become powerful when combined with generics, allowing you to create flexible type relationships and reduce the need for function overloads.
interface IdLabel { id: number; } interface NameLabel { name: string; } // Using conditional type instead of multiple overloads type NameOrId<T extends string | number> = T extends number ? IdLabel : NameLabel; function createLabel<T extends string | number>( idOrName: T ): NameOrId<T> { throw "unimplemented"; } // Usage let a = createLabel("typescript"); // type: NameLabel let b = createLabel(123); // type: IdLabel
Q: How does the infer keyword work in conditional types?
A: The infer keyword allows you to extract and reuse type information from the type you’re checking against. It’s commonly used to extract types from arrays, promises, and functions.
// Extract return type of a function type GetReturnType<Type> = Type extends (...args: any[]) => infer Return ? Return : never; // Usage type Num = GetReturnType<() => number>; // type: number type Str = GetReturnType<(x: any) => string>; // type: string // Extract array element type type Flatten<Type> = Type extends Array<infer Item> ? Item : Type; type NumberType = Flatten<number[]>; // type: number type StringType = Flatten<string>; // type: string
Q: What are distributive conditional types and how do they work with unions?
A: When conditional types operate on a generic type that is a union, they are automatically distributed over each member of that union. You can prevent this behavior by wrapping types in square brackets.
// Distributive behavior type ToArray<Type> = Type extends any ? Type[] : never; type StrNumArr = ToArray<string | number>; // type is string[] | number[] // Non-distributive behavior type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never; type StrNumArrNonDist = ToArrayNonDist<string | number>; // type is (string | number)[]
Q: How do conditional type constraints work and when should you use them?
A: Conditional type constraints allow you to check for the presence of properties or structures before accessing them, providing type-safe ways to work with potentially incomplete types.
// With constraint type MessageOf<T extends { message: unknown }> = T["message"]; // With conditional type to handle missing properties type MessageOf2<T> = T extends { message: unknown } ? T["message"] : never; interface Email { message: string; } interface Dog { bark(): void; } type EmailMessage = MessageOf2<Email>; // type: string type DogMessage = MessageOf2<Dog>; // type: never