DAY 29 OF 30
Conditional types and infer
Yesterday, in one sentence
A type that builds an object type by looping over a union of keys and giving each
key a type, written [K in keyof T]. You can add or remove ? and readonly as
you go. The built in utility types like Partial and Readonly are written this
way.
Did you say:
- ✓builds an object type by looping over a union of keys
- ✓written `[K in keyof T]`
- ✓can add or remove `?` and `readonly` as it goes
- ✓the built in utility types are written this way
Interview question
Generics let a type or function take a type as a parameter, so one definition works with many types without losing type safety. The caller supplies the type, or TypeScript infers it from the arguments.
Did you say:
- ✓a type taken as a parameter
- ✓one definition works across many types
- ✓keeps type safety, unlike `any`
- ✓usually inferred from the arguments
A conditional type chooses a type based on a check, written like a ternary operator
in JavaScript. It uses the extends keyword to check the type.
type Mutant = { power: string };
type IsMutant<T> = T extends Mutant ? true : false;
type Wolverine = IsMutant<{ power: "healing" }>; // true
type IronMan = IsMutant<{ name: "Tony" }>; // false
{ power: "healing" } has a power property, so it is assignable to Mutant, and
Wolverine returns the true branch. { name: "Tony" } does not, so IronMan returns
the false branch.
Watch out when the type is a unionUnion Type. The check runs once per member and the results are unioned together.
type Both = IsMutant<{ power: "flight" } | { name: "Tony" }>;
// true | false, which TypeScript collapses to boolean
infer declares a new type variable by inferring its type from the generic T
being checked. It can only be used inside the extends clause of a conditional type.
type ElementOf<T> = T extends Array<infer U> ? U : never;
type A = ElementOf<string[]>; // string
type B = ElementOf<number[]>; // number
Array<infer U> reads as an array of something, and call that something U. When
T is string[], U is inferred as string, and the true branch returns it. When
T is not an array, there isn't anything to fill U, which is what the false branch
is for.
The real use is deriving a type from a structure you did not write, so a change to it carries through automatically instead of leaving a hand written type behind.
// from a library, you did not write this
type ApiResponse = {
data: { name: string; level: number }[];
status: number;
};
type DataOf<T> = T extends { data: infer D } ? D : never;
type HeroList = DataOf<ApiResponse>;
// { name: string; level: number }[]
DataOf reads as give me the type of the data property on whatever object I pass
in, or never if it does not have one. If the library changes the type behind data,
HeroList changes with it.
Check yourself
Result here?
type Returns<T> = T extends () => infer R ? R : never
type Result = Returns<() => string[]>
The function's return position is captured by infer R. The return type is string[], so R is string[] and the true branch returns it.
infer do?A conditional type chooses between two types based on a check, written
T extends U ? X : Y, where extends checks whether T is assignable to U.
infer declares a new type variable by inferring its type from the one being
checked, and can only be used inside the extends clause of a conditional type.
It is useful for typing data that may change or comes from a third party library,
where you would rather derive the type than write it out by hand.
Did you say:
- ✓chooses between two types based on a check
- ✓written `T extends U ? X : Y`
- ✓`infer` declares a variable by inferring its type
- ✓useful for types that may change or come from a third party