We can create our own utility types by some advanced mapped type usages.
Basics
Let's see the basic example.
type Person = {
name: string;
gender: string;
age: number;
married: boolean;
}
For general uses, this data can be modified partial of it or only for display.
type PersonPartial = {
name?: string;
gender?: string;
age?: number;
married?: boolean;
}
type ReadonlyPerson = {
readonly name: string;
readonly gender: string;
readonly age: number;
readonly married: boolean;
}
This would write lots of duplicate code.
Reduce the duplicate code
Thus, we can use mapped types syntax to reference the original type.
type UsePartial<T> = {
[K in keyof T]?: T[K]
}
type UseReadonly<T> = {
readonly [K in keyof T]: T[K]
}
type PersonPartial = UsePartial<Person>
// { name?: string, ... , married?: boolean }
type ReadonlyPerson = UseReadonly<Person>
// { readonly name: string, ... , readonly married: boolean }
... in ...
syntax is similar to the JavaScript for. . .in
statement, which is used to iterate all types in type keyof T
keyof
operator is used to get all the keys of a type T
, and its return type is a union type.
T[K]
is similar to the syntax for attribute access, and is used to get the type of the value corresponding to an attribute of the object type.
And we can adding +
and -
prefixes to the modifiers ?
or readonly
to add or removed the modifiers.
type NotAllowPartial<T> = {
[K in keyof T]-?: T[K]
}
type AddReadonly<T> = {
+readonly [K in keyof T]: T[K]
}
If we don't add prefixes to the modifiers, the default is +
.
More tips to generate key types
And also, we can use as ...
syntax to generates the corresponding type from the type T
.
type UseGetter<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
type GetPerson = Getters<Person>
// {
// getName: () => string;
// getGender: () => string;
// getAge: () => number;
// getMarried: () => boolean;
// }
Because the Capitalize
utility type requires string
type for its parameter, and the return type by keyof
may includes string
, number
and symbol
type. we need to filter it by the string & ...
.
Bonus
If we return never type, it would be filter in the process of mapping keys.
type Status = 'excellent' | 'great' | 'bad'
// type Exclude<T, U> = T extends U ? never : T
type InfoGetterWithFilter<T, U> = {
[K in T as Exclude<T & string, U>]: string
}
type GetGoodStatusInfo = InfoGetterWithFilter<Status, 'bad'>
// {
// excellent: string;
// great: string;
// }