TypeScript - マップ型:初学者向けガイド

こんにちは、未来のTypeScript魔法使いさんたち!今日は、マップ型の世界に興味深く飛び込んでみましょう。プログラミングが初めての方でも心配しないでください。私はあなたの親切なガイドとして、ステップバイステップで進めていきます。このチュートリアルの終わりまでに、プロのように型をマッピングできるようになるでしょう!

TypeScript - Mapped Types

マップ型とは?

まず、マップ型とは何かを理解しましょう。箱の中に色々なチョコレートが入っているとします。そして、それぞれのチョコレートを金の包装紙で包む新しい箱を作りたいとします。これがTypeScriptにおけるマップ型の仕事です。既存の型を基にして、一定のルールに基づいて新しい型を生成します。

ビルトインマップ型

TypeScriptには、非常に便利ないくつかの定義済みマップ型があります。一つずつ見ていきましょう。

1. Partial

Partial<T>型は、Tのすべてのプロパティをオプションにします。厳しいレシピを柔軟なものに変えるようなものです。

interface Recipe {
name: string;
ingredients: string[];
cookingTime: number;
}

type FlexibleRecipe = Partial<Recipe>;

// すべてのプロパティを指定しなくても良い
const quickSnack: FlexibleRecipe = {
name: " Toast",
// ingredientsとcookingTimeは省略可
};

この例では、FlexibleRecipeを使うことで、すべてのプロパティを指定しなくても良いです。オブジェクトの一部を更新したいときに非常に便利です。

2. Required

Required<T>型は、Partial<T>の逆で、元の型でオプションだったプロパティも必須にします。

interface UserProfile {
name: string;
age?: number;
email?: string;
}

type CompleteUserProfile = Required<UserProfile>;

// すべてのプロパティを提供する必要があります
const user: CompleteUserProfile = {
name: "Alice",
age: 30,
email: "[email protected]"
};

ここで、CompleteUserProfileは、元のUserProfileでオプションだったageemailも含めてすべてのプロパティを提供する必要があります。

3. Readonly

Readonly<T>型は、Tのすべてのプロパティをリードオンリーにします。オブジェクトをガラスケースに入れるようなものです。見ることはできますが、触ることはできません。

interface Toy {
name: string;
price: number;
}

type CollectibleToy = Readonly<Toy>;

const actionFigure: CollectibleToy = {
name: "Superhero",
price: 19.99
};

// 以下はエラー becomes
// actionFigure.price = 29.99;

この例では、一度actionFigureを作成すると、プロパティを変更することができません。不変オブジェクトを作成するのに非常に便利です。

4. Pick<T, K>

Pick<T, K>型は、Tから指定されたプロパティKだけを選択して新しい型を作成します。お気に入りの機能を選別するようなものです。

interface Smartphone {
brand: string;
model: string;
year: number;
color: string;
price: number;
}

type BasicPhoneInfo = Pick<Smartphone, 'brand' | 'model'>;

const myPhone: BasicPhoneInfo = {
brand: "TechBrand",
model: "X2000"
};

ここで、BasicPhoneInfoSmartphonebrandmodelプロパティだけを含み、他は除きます。

5. Omit<T, K>

Omit<T, K>型は、Pick<T, K>型の逆で、Tから指定されたプロパティKを除いて新しい型を作成します。

interface Book {
title: string;
author: string;
pages: number;
isbn: string;
}

type BookPreview = Omit<Book, 'pages' | 'isbn'>;

const preview: BookPreview = {
title: "TypeScript Adventures",
author: "Code Wizard"
};

この場合、BookPreviewBookのすべてのプロパティを含みますが、pagesisbnを除きます。

マップ型の使用例

ビルトインマップ型を見てきたので、現実のシナリオでどのように使用できるかを見てみましょう。

例1: フォーム状態の作成

フォームを作成して、どのフィールドが編集されたかを追跡したいとします:

interface LoginForm {
username: string;
password: string;
rememberMe: boolean;
}

type FormTouched = { [K in keyof LoginForm]: boolean };

const touchedFields: FormTouched = {
username: true,
password: false,
rememberMe: true
};

ここで、FormTouched型はLoginFormと同じキーを持っていますが、すべての値がブール型で、フィールドがタッチされたかどうかを示します。

例2: API レスポンスラッパー

APIが異なるデータ型を返す場合、それぞれのレスポンスを標準的な形式でラップしたいとします:

interface ApiResponse<T> {
data: T;
status: 'success' | 'error';
timestamp: number;
}

type UserData = { id: number; name: string; email: string };
type ProductData = { id: number; name: string; price: number };

const userResponse: ApiResponse<UserData> = {
data: { id: 1, name: "John Doe", email: "[email protected]" },
status: 'success',
timestamp: Date.now()
};

const productResponse: ApiResponse<ProductData> = {
data: { id: 101, name: "Laptop", price: 999.99 },
status: 'success',
timestamp: Date.now()
};

この例では、ジェネリックを使用して、柔軟で再利用可能な型構造を作成しています。

カスタムマップ型の作成

さあ、TypeScriptの力を試して、カスタムマップ型を作成しましょう!

カスタム型1: Nullable

すべてのプロパティをnullにできる型を作成しましょう:

type Nullable<T> = { [K in keyof T]: T[K] | null };

interface Person {
name: string;
age: number;
}

type NullablePerson = Nullable<Person>;

const maybePerson: NullablePerson = {
name: "Jane",
age: null  // これは現在有効です
};

私たちのNullable<T>型は、すべてのプロパティが元の型またはnullのいずれかを持つことができます。

カスタム型2: Freezable

オブジェクトにfreezeメソッドを追加する型を作成しましょう:

type Freezable<T> = T & { freeze(): Readonly<T> };

interface Config {
theme: string;
fontSize: number;
}

function makeFreezable<T>(obj: T): Freezable<T> {
return {
...obj,
freeze() {
return Object.freeze({ ...this }) as Readonly<T>;
}
};
}

const config = makeFreezable<Config>({
theme: "dark",
fontSize: 14
});

const frozenConfig = config.freeze();
// frozenConfig.theme = "light";  // これはエラーを引き起こします

このカスタム型は、任意のオブジェクトにfreezeメソッドを追加し、不変なバージョンを作成することができます。

結論

哇、今日は多くのことをカバーしました!ビルトインマップ型からカスタム型まで、TypeScriptがどれほど強力で柔軟であるかを感じていただけたでしょうか。マップ型は、TypeScriptのツールボックスにおける魔法の杖のような存在で、型を変換し、Manipulateすることが非常に便利な方法でできます。

マップ型をマスターする鍵は練習です。自分で作成してみたり、異なる組み合わせを試してみたりして、機能的で優雅で型安全なTypeScriptコードを書けるようになるまでががんばりましょう。

codingを続け、学び続け、そして最も重要なのは、TypeScriptを楽しみましょう!

Credits: Image by storyset