Homomorphic and non-homomorphic mapped types
type type User = {
readonly name: string;
age?: number;
}
User = {
readonly name: stringname: string
age?: number | undefinedage?: number
}
type type Mapped = {
readonly name: string;
age?: number | undefined;
}
Mapped = { [function (type parameter) KeyKey in keyof type User = {
readonly name: string;
age?: number;
}
User]: type User = {
readonly name: string;
age?: number;
}
User[function (type parameter) KeyKey] } So nobody ever told you abouthomomorphic mapped typesandnon-homomorphic mapped types? These concepts live inside mapped types and they matter a lot. Let's explore the reason behind a behavior that looks weird at first glance: sometimes the modifiers('readonly'and'?')are kept in a mapped type, and sometimes they aren't.
Note: before we go on, you should already know what mapped types are, deal?
What is a homomorphic mapped type?
A homomorphic mapped type is a mapped type where the compiler can tell you're mapping over the properties of an existing object, so it can keep those properties' modifiers.
Now in practice, here's an example of a mapped type that is homomorphic:
type type User = {
readonly name: string;
age?: number;
}
User = {
readonly name: stringname: string
age?: number | undefinedage?: number
}
type type Homomorphic = {
readonly name: string;
age?: number | undefined;
}
Homomorphic = { [function (type parameter) KeyKey in keyof type User = {
readonly name: string;
age?: number;
}
User]: type User = {
readonly name: string;
age?: number;
}
User[function (type parameter) KeyKey] }
// the compiler can recognize the keys
// of User and keep their modifiers (readonly and ?)
// {
// readonly name: string;
// age?: number | undefined;
// }What is a non-homomorphic mapped type?
A non-homomorphic mapped type is the opposite: the mapped type doesn't keep the modifiers. Let's see an example:
type type User = {
readonly name: string;
age?: number;
}
User = {
readonly name: stringname: string
age?: number | undefinedage?: number
}
type type UserKeys = keyof UserUserKeys = keyof type User = {
readonly name: string;
age?: number;
}
User // "name" | "age"
type type NonHomomorphic = {
name: string;
age: number | undefined;
}
NonHomomorphic = { [function (type parameter) KeyKey in type UserKeys = keyof UserUserKeys]: type User = {
readonly name: string;
age?: number;
}
User[function (type parameter) KeyKey] }
// here the compiler can't make that
// connection: all it sees is the
// union "name" | "age", so the
// modifiers are not kept
// {
// name: string;
// age: number | undefined;
// }After seeing the code above, you might be wondering:"you said the modifiers aren't kept, and they really weren't, so why was the age property inferred as number | undefined if the "?" modifier wasn't kept?"
Playground with the examples