Item 56: Pay Attention to How Types Display
May 10, 2024 ยท View on GitHub
Things to Remember
- There are many valid ways to display the same type. Some are clearer than others.
- TypeScript gives you some tools to control how types display, notably the
Resolvegeneric. Make judicious use of this to clarify type display and hide implementation details. - Consider handling important special cases of generic types to improve type display.
- Write tests for your generic types and their display to avoid regressions.
Code Samples
type T123 = '1' | '2' | '3';
// ^? type T123 = "1" | "2" | "3"
type T21 = '2' | '1';
// ^? type T21 = "2" | "1"
type T123 = '1' | '2' | '3';
// ^? type T123 = "2" | "1" | "3"
type PartiallyPartial<T, K extends keyof T> =
Partial<Pick<T, K>> & Omit<T, K>;
interface BlogComment {
commentId: number;
title: string;
content: string;
}
type PartComment = PartiallyPartial<BlogComment, 'title'>;
// ^? type PartComment =
// Partial<Pick<BlogComment, "title">> &
// Omit<BlogComment, "title">
type Resolve<T> = T extends Function ? T : {[K in keyof T]: T[K]};
type PartiallyPartial<T, K extends keyof T> =
Resolve<Partial<Pick<T, K>> & Omit<T, K>>;
type PartComment = PartiallyPartial<BlogComment, 'title'>;
// ^? type PartComment = {
// title?: string | undefined;
// commentId: number;
// content: string;
// }
type ObjIdentity<T> = {[K in keyof T]: T[K]};
type S = ObjIdentity<string>;
// ^? type S = string
type N = ObjIdentity<number>;
// ^? type N = number
type U = ObjIdentity<'A' | 'B' | 'C'>;
// ^? type U = "A" | "B" | "C"
type F = ObjIdentity<(a: number) => boolean>;
// ^? type F = {}
type D = Resolve<Date>;
// ^? type D = {
// toLocaleString: {
// (locales?: Intl.LocalesArgument,
// options?: Intl.DateTimeFormatOptions | undefined): string;
// (): string;
// (locales?: string | string[] | undefined,
// options?: Intl.DateTimeFormatOptions | undefined): string;
// };
// ... 42 more ...;
// [Symbol.toPrimitive]: {
// ...;
// };
// }
interface Color { r: number; g: number; b: number; a: number };
type Chan = keyof Color;
// ^? type Chan = keyof Color
type ChanInline = Resolve<keyof Color>;
// ^? type ChanInline = "r" | "g" | "b" | "a"
type FullComment = PartiallyPartial<BlogComment, never>;
// ^? type FullComment = {
// title: string;
// commentId: number;
// content: string;
// }
type PartiallyPartial<T extends object, K extends keyof T> =
[K] extends [never]
? T // special case
: T extends unknown // extra conditional to preserve distribution over unions
? Resolve<Partial<Pick<T, K>> & Omit<T, K>>
: never;
type FullComment = PartiallyPartial<BlogComment, never>;
// ^? type FullComment = BlogComment