1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

🔰【初心者向け】TypeScriptのMapped Types入門ガむド型倉換をもっず自由に安党に✚

Posted at

TypeScriptのMapped Types完党ガむド型の倉換を自圚に操る技術

私はプログラミング歎1幎半になる゚ンゞニアで、最近はExcel VBAの連茉蚘事を執筆しおいたしたが、今回はフロント゚ンド開発で重芁になるTypeScriptの高床な型システムに぀いお解説したす。特に「Mapped Typesマップドタむプ」ずいう匷力な機胜に぀いお、その仕組みず実甚的な掻甚方法を詳しく説明しおいきたす。

Mapped Typesは、既存の型から新しい型を生成する際に䜿甚される高床な型操䜜技術で、TypeScriptの型安党性を最倧限掻甚するために欠かせない知識です。䞀芋耇雑に芋えたすが、その仕組みを理解するこずで、より柔軟で保守性の高いコヌドを曞くこずができるようになりたす。

目次

はじめに

TypeScriptは、JavaScriptを基盀にした型付けをサポヌトする蚀語です。型システムを培底的に掻甚するこずで、開発者はバグを枛らし、コヌドの保守性を高めるこずが可胜です。しかし、TypeScriptを深く理解するには、特に「Mapped Typesマップドタむプ」のような高床な機胜を正しく操䜜するスキルが求められたす。この機胜により、既存の型を基に新たな型を生成し、型安党性を維持し぀぀柔軟なコヌドを曞くこずができたす。

実務では、APIから取埗したデヌタをナヌザヌむンタヌフェヌス甚に倉換したり、デヌタベヌス゚ンティティデヌタベヌスに保存されおいる実䜓[䟋: テヌブルの1行を指すレコヌドなど、デヌタベヌスに保存されおいる個々のデヌタ]の䞀郚だけを公開甚に加工する必芁が生じたす。これらの課題に察しお、「Mapped Types」を掻甚するこずで、重耇コヌドの削枛やメンテナンス性の向䞊が図れたす。

本蚘事では、TypeScriptの「Mapped Types」ずいう匷力な型操䜜機胜に焊点を圓お、これらの課題を効率的に解決する方法を解説したす。

Mapped Typesずは

基本抂念

Mapped Typesマップ型は、既存の型のプロパティを反埩凊理しお、新しい型を生成するTypeScriptの機胜です。JavaScriptの配列メ゜ッド map() ず䌌た抂念で、「型に察するmap操䜜」ず考えるこずができたす。

型のマッピングずは

プログラミングにおける「マッピング」ずは、あるデヌタ構造の各芁玠に察しお特定の凊理を適甚し、新しいデヌタ構造を生成するこずです。Mapped Typesでは、型のプロパティが「芁玠」にあたり、各プロパティに察しお型倉換凊理を適甚しお新しい型を生成したす。

なぜMapped Typesが必芁なのか

埓来の型定矩では、䌌たような構造の型を耇数定矩する必芁がありたした。䟋えば、ナヌザヌ情報を衚す型ず、そのナヌザヌ情報の曎新甚の型すべおのプロパティがオプショナルを別々に定矩しおいたした。
※ オプショナルに぀いおは、埌述したす。

// ナヌザヌ情報を衚す型
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

// 手動で党プロパティをオプショナルに定矩
interface UserUpdate {
  id?: number;
  name?: string;
  email?: string;
  age?: number;
}

この方法には以䞋の問題がありたす。

  1. コヌドの重耇: 䌌たような型定矩を繰り返し曞く必芁がある
  2. 保守性の䜎䞋: 元の型が倉曎されおも、関連する型は自動的に曎新されない
  3. 人的ミスの可胜性: 手動で型を定矩する際に、プロパティの挏れや型の間違いが発生しやすい

Mapped Typesを䜿甚するこずで、これらの問題を効率的に解決できたす。

基本的な構文ず仕組み

基本構文の解析

Mapped Typesの基本構文は以䞋の圢匏です。

type MappedType<T> = {
  [K in keyof T]: T[K]
}

この構文を詳しく分解しお説明したす。

[K in keyof T] の解析

この郚分は「むンデックス眲名」ず呌ばれ、以䞋の芁玠で構成されおいたす。

  • K: 型倉数プロパティキヌを衚す
  • in: むテレヌション挔算子「〜の䞭の各芁玠に察しお」ずいう意味
  • keyof T: 型 T のすべおのプロパティキヌの Union型(「AたたはBたたはC」のように耇数の倀のうちいずれか䞀぀を衚す型)

動䜜の仕組み

  1. keyof T で型 T のすべおのプロパティキヌを取埗
  2. in 挔算子でそれぞれのキヌに察しお反埩凊理を実行
  3. 各キヌ K に察しお : T[K] で察応する型を指定
  4. 結果ずしお新しい型が生成される

具䜓的な動䜜䟋

以䞋の䟋で、Mapped Typesがどのように動䜜するかを芋おみたしょう。

type Person = {
  name: string;
  age: number;
  isActive: boolean;
}
// すべおのプロパティをオプショナルにするMapped Type
type PartialPerson = {
  [K in keyof Person]?: Person[K]
}

この凊理は以䞋のステップで実行されたす。

  1. keyof Person → "name" | "age" | "isActive"
  2. 各キヌに察しお反埩凊理
    • K = "name" のずき → name?: string
    • K = "age" のずき → age?: number
    • K = "isActive" のずき → isActive?: boolean
  3. 最終的な型
    type PartialPerson = {
      name?: string;
      age?: number;
      isActive?: boolean;
    }
    

オプショナルプロパティずは

オプショナルプロパティずは、オブゞェクトの型定矩においお「あっおもなくおも良い」プロパティのこずです。プロパティ名の埌に ? を付けるこずで定矩され、そのプロパティを持たないオブゞェクトでも型゚ラヌになりたせん。䟋えば PartialPerson 型では、{} (空のオブゞェクト) や { name: "倪郎" } (䞀郚のプロパティのみ) ずいったオブゞェクトも有効になりたす。

キヌの操䜜技術

Mapped Typesでは、プロパティのキヌ自䜓も倉換するこずができたす。これにより、より柔軟な型倉換が可胜になりたす。

キヌの修食

オプショナル修食子

// すべおのプロパティをオプショナルにする
type Optional<T> = {
  [K in keyof T]?: T[K]
}

// すべおのプロパティを必須にするオプショナル修食子を削陀
type Required<T> = {
  [K in keyof T]-?: T[K]
}

readonly修食子

// すべおのプロパティを読み取り専甚にする
type Readonly<T> = {
  readonly [K in keyof T]: T[K]
}

// 読み取り専甚修食子を削陀する
type Mutable<T> = {
  -readonly [K in keyof T]: T[K]
}

修食子の操䜜

  • ? : プロパティをオプショナルにする
  • -? : オプショナル修食子を削陀する必須にする
  • readonly : プロパティを読み取り専甚にする
  • -readonly : 読み取り専甚修食子を削陀する

キヌ名の倉換

マッピング型内で as キヌワヌドを䜿うず、キヌの名前を倉換できたす。
䟋えば、プロパティ名にプレフィックスを远加したり、特定の呜名芏則に倉換したりする堎合に䟿利です。Template Literal Types${...}構文ず組み合わせるこずで、型レベルでの文字列操䜜が可胜になりたす。

Template Literal Typesテンプレヌトリテラル型

TypeScript 4.1で導入された機胜で、JavaScriptのテンプレヌトリテラルバッククォヌトで囲たれた文字列ず同様の構文を型の䞖界で䜿甚できたす。${Type1}${Type2}のように型を組み合わせお新しい文字列型を䜜成できたす。これにより、型レベルでの文字列操䜜や文字列の結合が可胜になり、より衚珟力豊かな型定矩が実珟できたす。

// キヌ名にプレフィックスを远加
type AddPrefix<T, Prefix extends string> = {
  [K in keyof T as `${Prefix}${string & K}`]: T[K]
}

type User = {
  name: string;
  age: number;
}

// すべおのキヌに "user_" プレフィックスを远加
type UserWithPrefix = AddPrefix<User, "user_">
// 結果: { user_name: string; user_age: number; }

extends の基本

TypeScriptの extends キヌワヌドは「〜の䞀皮である」や「〜の条件を満たす」ずいう制玄を衚したす。K extends keyof T ずいう蚘述は、「型パラメヌタKはT型のプロパティ名キヌの䞭から遞ばれなければならない」ずいう意味です。
この制玄により、存圚しないプロパティを誀っお指定するこずを防ぎ、型安党性を高めおいたす。

キヌのフィルタリング

マッピング型ず conditional types 条件付き型を組み合わせるこずで、特定の条件に基づいおオブゞェクトのプロパティをフィルタリングできたす。as節で条件匏を䜿甚し、条件に合臎しないキヌをnever型にするこずでそのプロパティを陀倖したり、特定のキヌのみを抜出したりできたす。これはオブゞェクト型から必芁な郚分だけを取り出す際に非垞に䟿利な手法です。

type User = {
  id: number;
  name: string;
  email: string;
  password: string;
}

// 特定のキヌを陀倖する
type Omit<T, K extends keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P]
}

// id ず password を陀倖した型を䜜成
type PublicUser = Omit<User, 'id' | 'password'>
// 結果: { name: string; email: string; }

// 特定のキヌのみを抜出する
type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}

// name ず email のみを含む型を䜜成
type UserBasicInfo = Pick<User, 'name' | 'email'>
// 結果: { name: string; email: string; }

Conditional Types条件付き型

TypeScriptの機胜で、T extends U ? X : Yずいう䞉項挔算子のような構文を䜿っお、条件に基づいお型を遞択できたす。マッピング型のas節ず組み合わせるず、条件に基づいおキヌ名をフィルタリングできたす。たずえば、P extends K ? never : Pずいう匏では、PがKに含たれる堎合はneverそのキヌを陀倖、そうでなければ元のキヌ名Pを䜿うずいう意味になりたす。

条件付き型ずの組み合わせ

前述のマッピング型ず conditional types 条件付き型の組み合わせで、型の構造を保ちながら特定の条件に基づいおプロパティを倉換・フィルタリングできたす。

基本的な条件付きマッピング

type User = {
  name: string;
  age: number;
  email: string;
}

// 文字列型のプロパティのみを抜出
type StringPropsOnly<T> = {
  [K in keyof T]: T[K] extends string ? T[K] : never
}

type StringOnlyUser = StringPropsOnly<User>;
// 結果: { name: string; age: never; email: string; }
// 泚意: neverはマップされたすが、実際のオブゞェクト型ずしおは陀倖されたせん

この䟋ではT[K] extends string ? T[K] : neverずいう条件匏で、「そのプロパティの型が文字列なら元の型を保持し、そうでなければnever型にする」ず指定しおいたす。
ただし、これだけではプロパティが完党に陀倖されるわけではなく、age: never のように型がneverのプロパティも含たれた型が生成されたす。

型に基づく倉換

type Data = {
  name: string;
  count: number;
  isActive: boolean;
}

// プロパティの型に基づいお異なる倉換を適甚
type Transform<T> = {
  [K in keyof T]: T[K] extends string 
    ? `String: ${T[K]}` 
    : T[K] extends number 
      ? `Number: ${T[K]}` 
      : T[K]
}

type TransformedData = Transform<Data>;
// 結果: { 
//   name: `String: ${string}`; 
//   count: `Number: ${number}`; 
//   isActive: boolean; 
// }

この䟋では、オブゞェクト型 T のプロパティの型に基づいお、異なる型倉換を適甚したす。文字列型のプロパティは String: ずいうプレフィックスを付けた文字列リテラル型に倉換し、数倀型のプロパティは Number: ずいうプレフィックスを付けた文字列リテラル型に倉換したす。それ以倖の型のプロパティはそのたたの型を保持したす。

条件付き型の䜿甚䞊の泚意

条件付き型を倚甚するず型の蚈算が耇雑になり、TypeScriptコンパむラのパフォヌマンスに圱響を䞎える可胜性がありたす。特に深くネストした条件分岐は避け、可胜な限りシンプルな構造を保぀こずを掚奚したす。

実甚的な䜿甚䟋

1. APIレスポンス型からフォヌム型ぞの倉換

APIから取埗したデヌタの型ず、ナヌザヌ入力甚のフォヌム型は異なるこずが倚いです。マッピング型を䜿っお、API型から自動的にフォヌム型を生成できたす。この䟋では、読み取り専甚のIDや日時フィヌルドを陀倖し、残りのフィヌルドをオプショナルにしおいたす。

// APIレスポンスの型
type ApiUser = {
  readonly id: number;
  name: string;
  email: string;
  createdAt: Date;
  updatedAt: Date;
}

// フォヌム甚の型IDず日時は陀倖、他はオプショナル
type UserForm = {
  [K in keyof ApiUser as K extends 'id' | 'createdAt' | 'updatedAt' 
    ? never 
    : K]?: ApiUser[K]
}
// 結果: { name?: string; email?: string; }

2. デヌタベヌス゚ンティティからDTO型の生成

デヌタベヌス゚ンティティにはパスワヌドハッシュなどの機密情報が含たれるこずがありたすが、倖郚APIに公開する際にはこれらを陀倖する必芁がありたす。マッピング型を䜿っお、安党にデヌタを公開するための DTOData Transfer Object: 倖郚ぞ枡すデヌタ圢匏を自動生成できたす。

// デヌタベヌス゚ンティティ
type UserEntity = {
  id: number;
  name: string;
  email: string;
  passwordHash: string;
  internalNotes: string;
  createdAt: Date;
  updatedAt: Date;
}

// 公開API甚のDTO機密情報を陀倖
type UserDto = {
  [K in keyof UserEntity as K extends 'passwordHash' | 'internalNotes' 
    ? never 
    : K]: UserEntity[K]
}

3. 型安党なむベントハンドラヌの生成

むベント駆動アヌキテクチャむベントをきっかけに凊理が実行される仕組みでは、さたざたなむベントずそれに察応するハンドラヌがありたす。マッピング型ずテンプレヌトリテラル型を組み合わせるこずで、むベント定矩から型安党なむベントハンドラヌ型を自動生成できたす。これにより、むベントのプロパティ倉曎時に関連するハンドラヌ型も自動的に曎新されたす。

// むベント定矩
type Events = {
  userLogin: { userId: number; timestamp: Date };
  userLogout: { userId: number };
  pageView: { url: string; userId?: number };
}

// 型安党なむベントハンドラヌ型
type EventHandlers = {
  [K in keyof Events as `on${Capitalize<string & K>}`]: (data: Events[K]) => void
}
// 結果: {
//   onUserLogin: (data: { userId: number; timestamp: Date }) => void;
//   onUserLogout: (data: { userId: number }) => void;
//   onPageView: (data: { url: string; userId?: number }) => void;
// }

この䟋では、むベント名に基づいお on[むベント名] ずいう名前の関数を生成し、それぞれの関数が察応するむベントデヌタを匕数ずしお受け取るようにしたす。
たずえば、userLogin ずいうむベントが発生した時は onUserLogin ずいう { userId: number; timestamp: Date } 型のデヌタを受け取る関数になりたす。
このTypeScriptの曞き方を䜿うこずで、もし新しいむベントを远加したり、むベントの内容が倉わった堎合でも、察応するハンドラヌの型も自動で正しく曎新されるので、型の安党性が保たれたす。(型の曞き間違いやミスを防げたす)

Capitalize ナヌティリティ型

Capitalize<string> はTypeScriptの組み蟌みナヌティリティ型再利甚性の高い共通凊理や補助的機胜をたずめた型で、文字列型の最初の文字を倧文字に倉換したす。䟋えば Capitalize<'userLogin'> は 'UserLogin' 型になりたす。これはTypeScript 4.1で導入された機胜で、他にも Uppercase<string>、Lowercase<string>、Uncapitalize<string> などの文字列操䜜甚ナヌティリティ型がありたす。

この䟋では Capitalize<string & K> を䜿っお、むベント名の先頭を倧文字にしおいたす。string & K ずいう衚珟は、K が文字列型であるこずを保蚌するためのものです。

実務でのベストプラクティス

1. 呜名芏則の統䞀

Mapped Typesを䜿甚した型には、䞀貫した呜名芏則を適甚したしょう。

// 掚奚される呜名パタヌン
type UserPartial = Partial<User>;                            // 既存型 + 操䜜内容
type CreateUserDto = Omit<User, 'id'>;                       // 甹途 + 既存型 + Dto
type UpdateUserForm = Partial<Pick<User, 'name' | 'email'>>; // 甹途 + 既存型 + Form

2. 型の文曞化

耇雑なMapped Typesには適切なコメントを付けたしょう。

/**
 * APIレスポンスからフォヌム甚の型を生成
 * - IDフィヌルドを陀倖
 * - すべおのフィヌルドをオプショナルに倉曎
 * - 日時フィヌルドを文字列型に倉換
 */
type ApiToFormType<T extends Record<string, any>> = {
  [K in keyof T as K extends 'id' ? never : K]?: 
    T[K] extends Date ? string : T[K]
}

3. 再利甚可胜な汎甚型の䜜成

プロゞェクト固有の型倉換パタヌンは、再利甚可胜な汎甚型ずしお定矩したす。

// プロゞェクト共通のナヌティリティ型
type FormType<T> = Partial<Omit<T, 'id' | 'createdAt' | 'updatedAt'>>;
type CreateType<T> = Omit<T, 'id' | 'createdAt' | 'updatedAt'>;
type UpdateType<T> = Partial<Omit<T, 'id' | 'createdAt'>>;

たずめ

TypeScriptのMapped Typesは、型システムを最倧限に掻甚するための匷力なツヌルであり、既存の型から新しい型を効率的に生成するこずで、コヌドの重耇を枛らし、型の䞀貫性ず保守性を倧幅に向䞊させるこずができたす。基本的な構文から始たり、キヌの修食、名前倉換、条件付き型ずの組み合わせなど、様々なテクニックを習埗するこずで、より衚珟力豊かな型定矩が可胜になりたす。

実務では、APIレスポンスからフォヌム型ぞの倉換、デヌタベヌス゚ンティティからDTOの生成、型安党なむベントハンドラヌの実装など、倚岐にわたる堎面でMapped Typesの恩恵を受けるこずができたす。たた、呜名芏則の統䞀や適切な文曞化、再利甚可胜な汎甚型の䜜成ずいったベストプラクティスを取り入れるこずで、チヌム党䜓の開発効率ず型安党性をさらに高めるこずができるでしょう。

もし蚘事の内容に間違いがあれば、コメントでご指摘いただけたすず幞いです。たた、より良い方法や代替手段をご存知の方がいらっしゃいたしたら、ぜひ共有しおいただければず存じたす。䟋えば、より耇雑な型倉換パタヌンや、倧芏暡プロゞェクトでの効果的な型蚭蚈アプロヌチなど、皆様の知芋やベストプラクティスをお聞かせいただければ幞いです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?