はじめに
Generic と Utility Types について、それぞれ聞いたことはあるけど詳しい内容や関係性についてはよく知らなかったので、学習がてらまとめてみた。
1. Genericとは
型を後から渡せる仕組み。
- Genericが便利なのは、「型の関係」を表せるから
- 入力の型 → T → 出力の型
- 構造は同じだけど、中身の型だけ違うものを扱える
type ApiResponse<T> = {
data: T;
error: string | null;
};
と一度作っておけば、
ApiResponse<User>
ApiResponse<Product>
ApiResponse<Order>
ApiResponse<Comment>
と使い回せる。
2. Utility Typesとは
既存の型を加工して新しい型を作る仕組み。
TypeScriptに最初からたくさん用意されている。
ここでは、以下の基本的なUtility Typesについて学んでいく。
Partial
Required
Readonly
Pick
Omit
Record
ReturnType
2.1 Partial
type User = {
id: number;
name: string;
age: number;
};
これをPartialを用いて
type UpdateUser = Partial<User>;
とすると、
type UpdateUser = {
id?: number;
name?: string;
age?: number;
};
になる。
つまりPartialとは、全てoptionalにするUtility Typeである。
用途
例えば、ユーザー情報の更新を行うとき、
function updateUser(
id: number,
data: Partial<User>
) {
}
とすれば、
updateUser(1, {
name: "Taro"
});
のように、一部だけ更新することができる。
2.2 Required
Partial とは逆に、
type RequiredUser = Required<User>;
とすると、
{
id: number;
name: string;
age: number;
}
のように全て必須となる。
2.3 Readonly
type ReadonlyUser = Readonly<User>;
とすると、
const user: ReadonlyUser = {
id: 1,
name: "Hachiware",
age: 30
};
に対して、
user.name = "Usagi";
ができなくなる。
つまり、Readonlyは読み取り専用にするUtility Typeである。
2.4 Pick
type User = {
id: number;
name: string;
age: number;
email: string;
};
これを、
type UserPreview = Pick<User, "id" | "name">;
とすると、
type UserPreview = {
id: number;
name: string;
};
になる。
つまり、Pickは必要なプロパティだけ取り出すUtility Typeである。
2.5 Omit
OmitはPickの逆のようなもので、
type User = {
id: number;
name: string;
age: number;
email: string;
};
に対して、
type UserWithoutEmail = Omit<User, "email">;
とすると、
{
id: number;
name: string;
age: number;
}
になる。
つまり、指定したプロパティを除外するUtility Typeである。
2.6 Record
type UserRole = Record<string, string>;
これは、
{
[key: string]: string;
}
のような型になる。
例えば、以下のような形。
const roles: Record<string, string> = {
admin: "管理者",
user: "一般ユーザー",
guest: "ゲスト"
};
つまり、Recordはキーと値の型を指定して、オブジェクト型を作るUtility Typesである。
他にも、以下のような使い方がある。
type Status = "loading" | "success" | "error";
const labels: Record<Status, string> = {
loading: "読み込み中",
success: "成功",
error: "エラー"
};
この場合、Statusに新しい値を追加したのにlabelsを追加し忘れると、TypeScriptがエラーを出して教えてくれる。
2.7 ReturnType
function getUser() {
return {
id: 1,
name: "Taro"
};
}
これに、
type User = ReturnType<typeof getUser>;
とすると、
type User = {
id: number;
name: string;
};
になる。
つまり、
typeof getUser
で getUserという関数そのものの型を渡して、
ReturnType<...>
でその関数の戻り値の型を取り出している。
3. Generic と Utility Types のつながり
多くのUtility Typesは、Genericを利用して作られている。
例えば、
Partial<T>
という形。
TにUserを渡すと、
Partial<User>
になる。
つまり、
Generic
↓
型を受け取る仕組み
↓
Utility Types
↓
型を受け取って加工する
という関係。
Typescript側の目線から見ると、
Partialを用意
↓
「Tにどんな型が入るかは、使うときまで分からない」
↓
Generic <T> で受け取る
↓
Userが入ってくる
↓
Userのプロパティを全部optionalにした型を作る
という仕組み。
補足
ちなみに、Partialは概念的には以下のような仕組みとなっている。
type Partial<T> = {
[P in keyof T]?: T[P];
};
([P in keyof User]は、Userのキーを1個ずつPにいれて処理するイメージ。)
流れとしては、
Partial<User>
↓
T = User
↓
keyof T
↓
Userのキーを取得
↓
各プロパティをoptionalにする
↓
Partial<User>
となっている。