iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🎄

How Utility Types Like Pick and Partial are Implemented

に公開

Utility Types (Built-in Types)

TypeScript already provides commonly used types and highly versatile, convenient types (built into TypeScript by default).
Utility types are often implemented using a combination of features like keyof types, lookup types, Mapped Types, and Conditional Types.
https://zenn.dev/axoloto210/articles/advent-calender-2023-day17
Let's take a look at how commonly used built-in types are implemented.

Readonly Type

The Readonly<T> type makes the properties of the object type T passed as an argument readonly.

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

As seen above, the Readonly type is implemented using a combination of Mapped Types and lookup types.
The T[P] part is a lookup type. The type of a property with the property key P contained in object type T is T[P].
The [P in keyof T]: T[P] part is what's known as Mapped Types, where the properties of the object type T become T[P]. In other words, a copy of the object type T is being created here.
Mapped Types allow for the configuration of mapping modifiers, making it possible to add readonly and the optional property marker ?, or remove them using -readonly and -?. By adding readonly to the property keys of the copied type of T, the application of readonly to each property is achieved.
It is a known characteristic of Readonly<T> that it does not make the properties of objects contained within T's properties readonly (it is shallow), and the implementation shows this is because readonly is only applied to each of T's own property keys.

Pick Type

The Pick<T, K> type is a built-in type that creates a new object type by picking properties with keys specified by K from the object type T. K can be specified as a union type of keys from type T.

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

The implementation of the Pick type looks like this.
The K extends keyof T part imposes a constraint that K must be a subtype of the union type of T's property keys.
Through Mapped Types, an object type consisting only of properties whose keys make up K is obtained. The structure of copying object type T to create a new type is common with the Readonly<T> type, resulting in a similar implementation.

Partial Type

The Partial<T> type is a type that makes all properties of type T optional.

type Partial<T> = {
    [P in keyof T]?: T[P];
};

The implementation of the Partial<T> type is almost identical to that of the Readonly<T> type.
Each property of the object type T is made optional using Mapped Types and the ? mapping modifier.

Required Type

The Required<T> type is the opposite of the Partial<T> type; it makes optional properties required. Its implementation differs from the Partial type only in that the mapping modifier is -?.

type Required<T> = {
    [P in keyof T]-?: T[P];
};

Extract Type

The Extract<T, U> type is a built-in type that extracts types from type T that are subtypes of type U. A union type is often specified for T.

type Extract<T, U> = T extends U ? T : never;

The Extract<T, U> type is implemented using Conditional Types.
Conditional Types use a syntax similar to the ternary operator. For example, in the case of T extends U ? T : never, if T is a subtype of U (satisfying the condition T extends U), it results in the T type; otherwise, it results in the never type.
These Conditional Types have a somewhat complex characteristic where the distribution of the union type occurs when a union type is specified for T.

type ToArray<T> = T extends string ? T[] : never

//type UnionArray = "octopus"[] | "squid"[]
type UnionArray = ToArray<"octopus"|"squid"> 

While the type of UnionArray might seem like it would be ("octopus"|"squid")[], the components of the union type are actually distributed, resulting in the type "octopus"[] | "squid"[].
The Extract<T, U> type is significant because it allows for type extraction through Conditional Types, even without being aware of the distributive property of union types.

Exclude Type

The Exclude<T, U> type is a built-in type that, conversely to Extract<T, U>, removes types from type T that are subtypes of type U.

type Exclude<T, U> = T extends U ? never : T;

The implementation is designed so that the result is the opposite of the Extract<T, U> case.

Omit Type

The Omit<T, K> type, opposite to the Pick<T, K> type, returns an object type excluding properties with keys specified by K.

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

The implementation of Omit<T, K> uses Pick and Exclude.
Unlike the Pick type, the constraint imposed on K in the Omit type is K extends keyof any, allowing for property keys other than those in T to be specified.
The Exclude<keyof T, K> part yields a type where the keys specified by K are removed from the property keys of T.
In other words, Pick<T, Exclude<keyof T, K>> constructs a new object type consisting only of properties from T that do not have keys included in K.

Other Utility Types

Utility types take care of highly versatile but complex-to-implement typings.
In addition to the types we looked at this time, there are many other utility types provided by TypeScript, so it might be a good idea to see if there is a type you can use before defining a complex type yourself.
https://www.typescriptlang.org/docs/handbook/utility-types.html

GitHubで編集を提案

Discussion