- Day 24:Utility Typesで「Userまわりの型設計」を一気に楽にする
- User型をベースとして用意する
- Utility Typesのざっくりイメージ
- Step 1:CreateUser型を作る
- Step 2:UpdateUser型を作る
- Step 3:UserSummary型を作る
- Step 4:Readonlyを使って「変更禁止」を表現する
- Step 5:User / CreateUser / UpdateUser / UserSummary をまとめて使ってみる
- Day 24のまとめ
- Day 24:Utility Types演習「既存型から5種類以上の新しい型を作る」
- ベースとなる既存型を決める:User型
- Step 1:Partialで「一部だけ更新したいUser」を作る
- Step 2:Requiredで「すべて必須のUser」を作る
- Step 3:Readonlyで「変更禁止のUser」を作る
- Step 4:Pickで「表示用の軽いUser」を作る
- Step 5:Omitで「作成用のUser」を作る
- Step 6:Utility Typesを組み合わせて、さらに派生型を増やす
- Step 7:作った型を実際に使ってみる
- Day 24演習のまとめ
Day 24:Utility Typesで「Userまわりの型設計」を一気に楽にする
第4週のDay 24では、TypeScriptの便利機能である Utility Types を使って、 ひとつの User 型から、いくつもの派生型をスマートに作っていきます。
扱うUtility Typesは次の5つです。
PartialRequiredReadonlyPickOmit
そして、実践テーマはこうです。
User型から
- User
- CreateUser
- UpdateUser
- UserSummary を作る。
「同じような型を何度も手書きする」のではなく、 「元の型をうまく再利用する」 という発想を身につけていきます。
User型をベースとして用意する
まずは、ベースとなる User 型を決めます。 ここでは、シンプルなユーザー情報を想定します。
// ベースとなるUser型
type User = {
id: number; // ユーザーID(DBで採番されるイメージ)
name: string; // 名前
email: string; // メールアドレス
age?: number; // 年齢(任意)
isActive: boolean; // 有効ユーザーかどうか
};
TypeScriptこの User をもとに、 「作成用」「更新用」「表示用」などの派生型をUtility Typesで作っていきます。
Utility Typesのざっくりイメージ
Partial
Partial<T> は、
「Tのすべてのプロパティを任意(オプショナル)にした型」
です。
type PartialUser = Partial<User>;
TypeScriptPartialUser は、User の各プロパティがすべて ? 付きになったイメージです。
Required
Required<T> は、
「Tのすべてのプロパティを必須にした型」
です。
type RequiredUser = Required<User>;
TypeScriptUser で age? のように任意だったものも、 RequiredUser では必須になります。
Readonly
Readonly<T> は、
「Tのすべてのプロパティを読み取り専用にした型」
です。
type ReadonlyUser = Readonly<User>;
TypeScriptReadonlyUser のプロパティは、代入で変更できなくなります。
Pick
Pick<T, K> は、
「Tから指定したプロパティだけを取り出した型」
です。
type UserNameAndEmail = Pick<User, "name" | "email">;
TypeScriptUser のうち、name と email だけを持つ型になります。
Omit
Omit<T, K> は、
「Tから指定したプロパティを除いた型」
です。
type UserWithoutEmail = Omit<User, "email">;
TypeScriptUser から email を取り除いた型になります。
Step 1:CreateUser型を作る
「作成時に必要な情報」を考える
ユーザー作成時には、通常 id はまだありません(DB側で採番されることが多いです)。 そのため、CreateUser では、
idは不要name/email/isActiveは必須ageは任意でもよい
というイメージで設計してみます。
OmitとRequiredを組み合わせる
// Userからidを除いた型
type UserWithoutId = Omit<User, "id">;
// 作成用のUser(すべて必須にしたい場合)
type CreateUser = Required<UserWithoutId>;
TypeScriptここでの流れはこうです。
Omit<User, "id">で「idを除いたUser」を作るRequired<...>で「残りのプロパティをすべて必須」にする
これにより、CreateUser は次のようなイメージになります。
// 展開イメージ(実際には自動で生成されます)
type CreateUser = {
name: string;
email: string;
age: number; // 必須に変わる
isActive: boolean;
};
TypeScriptポイント:
- 「作成時にはidはいらない」というビジネスルールを、型に落とし込めています。
Requiredを使うことで、「作成時には全部ちゃんと渡してね」という制約を型で表現できます。
Step 2:UpdateUser型を作る
「更新時に必要な情報」を考える
更新時には、
idは必須(どのユーザーを更新するかを特定するため)- それ以外の項目は「変更したいものだけ渡せばよい」
というケースが多いです。
つまり、
「idは必須、それ以外は任意」
という型を作りたいわけです。
PartialとPickを組み合わせる
// idだけを必須にしたいので、まずはidだけの型を作る
type UserIdOnly = Pick<User, "id">;
// id以外のプロパティを取り出す
type UserWithoutIdForUpdate = Omit<User, "id">;
// 更新用のUser型
type UpdateUser = UserIdOnly & Partial<UserWithoutIdForUpdate>;
TypeScriptここでの流れはこうです。
Pick<User, "id">で「idだけを持つ型」を作るOmit<User, "id">で「id以外のプロパティ」を取り出すPartial<...>で「id以外のプロパティをすべて任意」にする&(Intersection)で「id必須 + その他任意」を合成する
UpdateUser のイメージは次のようになります。
// 展開イメージ
type UpdateUser = {
id: number; // 必須
name?: string; // 任意
email?: string; // 任意
age?: number; // 任意
isActive?: boolean;// 任意
};
TypeScript重要ポイント:
Partialは「更新用の型」と相性がとても良いです。- 「どの項目を更新するかは呼び出し側に任せる」という柔らかい設計ができます。
- それでも
idは必須なので、「どのユーザーか分からない更新」はコンパイル時に防げます。
Step 3:UserSummary型を作る
「一覧表示用の軽い情報」を考える
ユーザー一覧画面などでは、 すべての詳細情報を表示する必要はなく、 たとえば次のような項目だけで十分なことが多いです。
idnameemail
ここでは、「一覧表示用の軽い型」として UserSummary を作ってみます。
Pickで必要な項目だけを取り出す
// 一覧表示用のUserSummary
type UserSummary = Pick<User, "id" | "name" | "email">;
TypeScriptこれだけで、
// 展開イメージ
type UserSummary = {
id: number;
name: string;
email: string;
};
TypeScriptという型が自動的に生成されます。
ポイント:
Pickは「表示用」「レスポンス用」など、 特定の場面で使う軽量な型を作るときにとても便利です。- 元の
User型を変更せずに、 「この場面ではこれだけあればいい」という型を簡単に作れます。
Step 4:Readonlyを使って「変更禁止」を表現する
「読み取り専用のUser」を考える
たとえば、「ログ出力用」や「履歴表示用」など、 「絶対に変更してほしくないUser情報」を扱う場面もあります。
そのときに使えるのが Readonly<User> です。
// 読み取り専用のUser
type ReadonlyUser = Readonly<User>;
TypeScriptReadonlyUser のプロパティは、代入で変更できなくなります。
const u: ReadonlyUser = {
id: 1,
name: "Taro",
email: "[email protected]",
isActive: true,
};
// u.name = "Hanako"; // コンパイルエラー:Readonlyなので変更不可
TypeScriptポイント:
Readonlyは「このオブジェクトは変更してはいけない」という意図を、 型としてはっきり表現できます。- セキュリティやバグ防止の観点でも、とても役に立つUtility Typeです。
Step 5:User / CreateUser / UpdateUser / UserSummary をまとめて使ってみる
最後に、ここまで作った型を使って、 簡単な関数をいくつか書いてみます。
// ベースUser型
type User = {
id: number;
name: string;
email: string;
age?: number;
isActive: boolean;
};
// CreateUser
type UserWithoutId = Omit<User, "id">;
type CreateUser = Required<UserWithoutId>;
// UpdateUser
type UserIdOnly = Pick<User, "id">;
type UserWithoutIdForUpdate = Omit<User, "id">;
type UpdateUser = UserIdOnly & Partial<UserWithoutIdForUpdate>;
// UserSummary
type UserSummary = Pick<User, "id" | "name" | "email">;
// ReadonlyUser
type ReadonlyUser = Readonly<User>;
// ===== 使用例 =====
// ユーザー作成関数
function createUser(data: CreateUser): User {
// 本来はDBなどでidを採番する
const newUser: User = {
id: Math.floor(Math.random() * 10000),
...data,
};
console.log("ユーザーを作成しました:", newUser);
return newUser;
}
// ユーザー更新関数
function updateUser(user: User, update: UpdateUser): User {
if (user.id !== update.id) {
throw new Error("IDが一致しません");
}
// updateの任意項目を上書きする
const updated: User = {
...user,
...update,
};
console.log("ユーザーを更新しました:", updated);
return updated;
}
// UserSummaryを返す関数
function toUserSummary(user: User): UserSummary {
return {
id: user.id,
name: user.name,
email: user.email,
};
}
// ReadonlyUserを使う例
function logUser(user: ReadonlyUser): void {
console.log("ユーザー情報(読み取り専用):", user);
}
// ===== 実際に使ってみる =====
const created = createUser({
name: "Taro",
email: "[email protected]",
age: 20,
isActive: true,
});
const updated = updateUser(created, {
id: created.id,
email: "[email protected]",
});
const summary = toUserSummary(updated);
console.log("UserSummary:", summary);
logUser(updated);
// updated.name = "Hanako"; // ReadonlyUserではないのでここはOKだが、ReadonlyUserならエラーになります
TypeScriptDay 24のまとめ
Day 24では、
Partial/Required/Readonly/Pick/OmitというUtility Typesの役割- ベースとなる
User型から、- 作成用の
CreateUser - 更新用の
UpdateUser - 一覧表示用の
UserSummaryを組み立てる流れ
- 作成用の
- 「ビジネスルール(作成時はid不要、更新時はid必須など)」を、 型として表現する考え方
を、具体的なコードとコメント付きで体験しました。
Utility Typesは、「同じような型を何度も手書きする」苦労を減らしてくれるだけでなく、 「意図を型に刻み込む」ための強力な道具でもあります。
今日の User 周りの設計をベースに、 ぜひ Product や Order など、他のドメインでも 「Create〜」「Update〜」「〜Summary」をUtility Typesで作ってみてください。 そうしていくうちに、型設計そのものがどんどん楽しくなっていくはずです。
Day 24:Utility Types演習「既存型から5種類以上の新しい型を作る」
Day 24の演習テーマは、とても実務寄りでおもしろい内容です。
ひとつの既存型から、 Utility Typesを使って5種類以上の新しい型を作る。
ここまで学んできた
PartialRequiredReadonlyPickOmit
を総動員して、「型の再利用力」を一気に高めていきます。
今回は、ひとつのベース型として User を用意し、そこから派生型をどんどん作っていく流れで説明していきます。
ベースとなる既存型を決める:User型
まずは、演習の土台となる既存型をひとつ決めます。 ここでは、シンプルなユーザー情報を表す User 型を使います。
// 既存のUser型(ベースになる型)
type User = {
id: number; // ユーザーID
name: string; // 名前
email: string; // メールアドレス
age?: number; // 年齢(任意)
isActive: boolean; // 有効ユーザーかどうか
};
TypeScriptこの User から、Utility Typesを使って 少なくとも5種類以上の新しい型を作っていきます。
Step 1:Partialで「一部だけ更新したいUser」を作る
Partialのイメージ
Partial<T> は、
「Tのすべてのプロパティを任意(オプショナル)にした型」
です。
更新処理などで、「変更したい項目だけ渡したい」という場面にぴったりです。
UpdateUserPartial型を作る
// Userのすべてのプロパティを任意にした型
type UpdateUserPartial = Partial<User>;
TypeScriptUpdateUserPartial のイメージは次のようになります。
// 展開イメージ(実際には自動で生成されます)
type UpdateUserPartial = {
id?: number;
name?: string;
email?: string;
age?: number;
isActive?: boolean;
};
TypeScriptポイント:
- 「どの項目を更新するかは呼び出し側に任せたい」ときに便利です。
- ただし、
idまで任意になってしまうので、 実際の更新用型では「idだけ必須」にするなどの工夫を組み合わせることが多いです。
Step 2:Requiredで「すべて必須のUser」を作る
Requiredのイメージ
Required<T> は、
「Tのすべてのプロパティを必須にした型」
です。
User では age? のように任意だったものも、 Required<User> では必須になります。
StrictUser型を作る
// すべてのプロパティが必須のUser
type StrictUser = Required<User>;
TypeScriptStrictUser のイメージは次のようになります。
// 展開イメージ
type StrictUser = {
id: number;
name: string;
email: string;
age: number; // 必須に変わる
isActive: boolean;
};
TypeScriptポイント:
- 「この場面では、全部の情報が揃っているUserしか扱わない」という きっちりした前提を型で表現できます。
- たとえば「DBから取得した後のUserは必ずageが入っている」というような場面で使えます。
Step 3:Readonlyで「変更禁止のUser」を作る
Readonlyのイメージ
Readonly<T> は、
「Tのすべてのプロパティを読み取り専用にした型」
です。
ReadonlyUser型を作る
// 読み取り専用のUser
type ReadonlyUser = Readonly<User>;
TypeScript使用例を見てみます。
const u: ReadonlyUser = {
id: 1,
name: "Taro",
email: "[email protected]",
isActive: true,
};
// u.name = "Hanako"; // コンパイルエラー:Readonlyなので変更不可
TypeScript重要ポイント:
- 「このUserは絶対に変更してほしくない」という意図を、 型としてはっきり表現できます。
- ログ出力用・履歴表示用など、「参照専用」の場面でとても役に立ちます。
Step 4:Pickで「表示用の軽いUser」を作る
Pickのイメージ
Pick<T, K> は、
「Tから指定したプロパティだけを取り出した型」
です。
一覧表示やサマリー表示など、「全部はいらないけれど、これだけ欲しい」という場面にぴったりです。
UserSummary型を作る
// 一覧表示用のUserSummary(id, name, emailだけ)
type UserSummary = Pick<User, "id" | "name" | "email">;
TypeScriptUserSummary のイメージは次のようになります。
// 展開イメージ
type UserSummary = {
id: number;
name: string;
email: string;
};
TypeScriptポイント:
- 「画面Aではこの項目だけ」「画面Bでは別の項目だけ」というように、 表示用の型を柔らかく作り分けることができます。
- 元の
User型を変更せずに、用途ごとの軽量な型を増やせるのが魅力です。
Step 5:Omitで「作成用のUser」を作る
Omitのイメージ
Omit<T, K> は、
「Tから指定したプロパティを除いた型」
です。
作成時には「まだ存在しないプロパティ」を除きたい場面にぴったりです。
CreateUser型を作る
ユーザー作成時には、通常 id はまだありません。 そこで、「idを除いたUser」を作成用の型にします。
// idを除いたUser(作成用)
type CreateUser = Omit<User, "id">;
TypeScriptCreateUser のイメージは次のようになります。
// 展開イメージ
type CreateUser = {
name: string;
email: string;
age?: number;
isActive: boolean;
};
TypeScriptポイント:
- 「作成時にはidはいらない」というビジネスルールを、 型として自然に表現できます。
- これにより、「idを手動で渡してしまう」といったミスをコンパイル時に防げます。
Step 6:Utility Typesを組み合わせて、さらに派生型を増やす
ここまでで、
UpdateUserPartial(Partial)StrictUser(Required)ReadonlyUser(Readonly)UserSummary(Pick)CreateUser(Omit)
の5種類ができました。
ここからは、Utility Typesを組み合わせて、 もう少し実務寄りの派生型を作ってみます。
UpdateUser型(id必須+その他任意)
更新用の型として、「idは必須、それ以外は任意」という形を作ります。
// idだけを必須にしたいので、まずはidだけの型を作る
type UserIdOnly = Pick<User, "id">;
// id以外のプロパティを取り出す
type UserWithoutIdForUpdate = Omit<User, "id">;
// 更新用のUser型(id必須+その他任意)
type UpdateUser = UserIdOnly & Partial<UserWithoutIdForUpdate>;
TypeScriptUpdateUser のイメージは次のようになります。
// 展開イメージ
type UpdateUser = {
id: number; // 必須
name?: string;
email?: string;
age?: number;
isActive?: boolean;
};
TypeScriptここが大事なポイントです:
PartialとPick/Omitを組み合わせることで、 「一部だけ必須」「残りは任意」という柔らかい型を作れます。- 更新処理では、「どの項目を更新するかは呼び出し側に任せる」一方で、 「どのユーザーかは必ず指定してもらう」という前提を型で表現できます。
Step 7:作った型を実際に使ってみる
最後に、ここまで作った型を使って、 簡単な関数をいくつか書いてみます。
// ベースUser型
type User = {
id: number;
name: string;
email: string;
age?: number;
isActive: boolean;
};
// 1. Partial
type UpdateUserPartial = Partial<User>;
// 2. Required
type StrictUser = Required<User>;
// 3. Readonly
type ReadonlyUser = Readonly<User>;
// 4. Pick
type UserSummary = Pick<User, "id" | "name" | "email">;
// 5. Omit
type CreateUser = Omit<User, "id">;
// 6. 組み合わせ:UpdateUser
type UserIdOnly = Pick<User, "id">;
type UserWithoutIdForUpdate = Omit<User, "id">;
type UpdateUser = UserIdOnly & Partial<UserWithoutIdForUpdate>;
// ===== 使用例 =====
// ユーザー作成関数
function createUser(data: CreateUser): User {
const newUser: User = {
id: Math.floor(Math.random() * 10000),
...data,
};
console.log("ユーザーを作成しました:", newUser);
return newUser;
}
// ユーザー更新関数(UpdateUserを使う)
function updateUser(user: User, update: UpdateUser): User {
if (user.id !== update.id) {
throw new Error("IDが一致しません");
}
const updated: User = {
...user,
...update,
};
console.log("ユーザーを更新しました:", updated);
return updated;
}
// UserSummaryを返す関数
function toUserSummary(user: User): UserSummary {
return {
id: user.id,
name: user.name,
email: user.email,
};
}
// ReadonlyUserを使うログ関数
function logUser(user: ReadonlyUser): void {
console.log("ユーザー情報(読み取り専用):", user);
}
// ===== 実際に使ってみる =====
const created = createUser({
name: "Taro",
email: "[email protected]",
age: 20,
isActive: true,
});
const updated = updateUser(created, {
id: created.id,
email: "[email protected]",
});
const summary = toUserSummary(updated);
console.log("UserSummary:", summary);
logUser(updated);
TypeScriptDay 24演習のまとめ
今回の演習では、
- ひとつの既存型
Userをベースにして、Partialで「全部任意の更新用」Requiredで「全部必須の厳密なUser」Readonlyで「変更禁止のUser」Pickで「表示用の軽量User」Omitで「作成用のUser」- 組み合わせで「id必須+その他任意のUpdateUser」 といった派生型を作りました。
- Utility Typesは、「同じような型を何度も手書きする」手間を減らすだけでなく、 ビジネスルール(作成時はid不要、更新時はid必須など)を型として表現するための道具であることも見てきました。
この演習をベースに、ぜひ
ProductOrderCustomer
など、別のドメインでも同じように
Create〜Update〜〜SummaryReadonly〜
といった派生型をUtility Typesで作ってみてください。 そうしていくうちに、「型設計そのものが楽しくなる感覚」が、じわっと育っていくはずです。
