TypeScriptの高度な型を解説

TypeScript用アイコン TypeScript
TypeScript eye catch
この記事は約11分で読めます。

この記事は、以下の方に向けて書いています。

TypeScriptの型についてもっと知りたい

RecordやPickのような型を使ってるサンプルコードが知りたい

はじめに

今回はTypeScriptで採用されている型で、少し学習コストの高い内容について解説します。

TypeScriptには、基本型をはじめ多くの型がありますが、

その中でも特殊で理解するのが難しいですが、使えるようになると便利な型がいくつかあります。

そんな型の使い道や、メリットデメリットについて説明していきます。

基本的な型について、知りたい方は、こちらを見てみてください。

Record

まずは、Record型についてです。

Record型は以下の形で定義されます。

type 型名 = Record<キー, 値>

JavaでいうところのMap型のようなイメージが分かりやすいと思います。

ここでは型の定義になるので、キーと値には型情報を記述します。

詳しくは、下記のサンプルコードで解説します。

サンプルコード

ここでは、Recordを使用したサンプルコードをいくつか紹介します。

実際に動かしてみて挙動を確かめてみてください。

動的なキー設定

まずRecordの定義をした後に、定義した型を使用したオブジェクトを作成します。

ここでのポイントは、オブジェクトのキーは何でも指定できる点です。

何でも指定できますので、このオブジェクトのキーはこれ、あのオブジェクトのキーはあれ、

などとオブジェクトによって切り替えることも可能になります。

type RecordSample = Record<string, string>;

const fruits: RecordSample = {
    a: 'banana',
    b: 'apple',
    c: 'cherry',
};

console.log(fruits.a);
// banana

特定のキー設定

ここではRecordのキーを特定の形で制限したものを紹介します。

この方法で定義することで、開発者によってキーの名前が異なると言う

リスクが多少緩和されます。

type RecordSample = Record<'a' | 'b' | 'c', string>;

const fruits: RecordSample = {
    a: 'banana',
    b: 'apple',
    c: 'cherry',
};

console.log(fruits.a);
// banana

この特定のキー設定をする場合は、オブジェクトには全てのキーが含まれている必要があります。

値に別の型を指定

キーはあくまでも判別用にしておいて、

値の部分に詳細情報を格納しておきたいと言う状況があると思います。

そのような場合には、下記のように定義することができます。

type Product = {
    name: string;
    price: number;
};

type RecordSample = Record<'a' | 'b' | 'c', Product>;

const fruits: RecordSample = {
    a: { name: 'banana', price: 120 },
    b: { name: 'apple', price: 135 },
    c: { name: 'cherry', price: 450 },
};

console.log(fruits.a);
// { name: 'banana', price: 120 }

また、キーの名前がコードを記述している段階で特定できない場合などは、

下記のようにブラケット記法を用いて、アクセスすることもできます。

const a = 'a'
console.log(fruits[a])
// { name: 'banana', price: 120 }

Pick

次にPick型の解説します。

簡単に言うと、元々使っていた型の一部分だけ抜き出したものになります。

Pick型は、正直なところ使わなくても良いですが、

上手く使うと可読性や保守性が上がるというメリットがあります。

Pick型は、以下のように定義されます。

Pick<[元になる型], [抽出したいプロパティの名前リスト]>

これだけだと分かりづらいと思いますので、サンプルコードを使って解説します。

サンプルコード

ここでは、サンプルコードの紹介をします。

一般的なPickの使い方

状況としては、元々使っていた型が存在していて、その中の一部だけ再利用して、

新しい型を使いたいという状況になります。

8行目でPick型を使って、「name」と「price」だけの新しい型を定義しています。

type Product = {
    name: string;
    price: number;
    address: string;
    qty: number;
};

type PickSample = Pick<Product, 'name' | 'price'>;

const newProduct: PickSample = {
    name: 'apple',
    price: 140,
}

console.log(newProduct);
// { name: 'apple', price: 140 }

特殊なPickの使い方

続いて、少し特殊なPick型の使い方を紹介します。

今回は、元々の型のプロパティの型が別の型だった時のサンプルコードです。

type Product = {
    name: string;
    price: number;
    address: string;
    qty: number;
    detail: Detail
};

type Detail = {
    note: string
}

type PickSample = Pick<Product, 'name' | 'price' | 'detail'>;

const newProduct: PickSample = {
    name: 'apple',
    price: 140,
    detail: { note: '備考' }
}

console.log(newProduct.detail.note);
// 備考

メリットと注意点

Pick型のメリットとしては、APIからのレスポンスの一部だけを、

別のコンポーネントへ渡したい時に便利です。

別のやり方もありますが、Pick型を使うことで可読性が大幅に上がると思います。

注意点として、主に以下の3つだけ気を付けておくと良いと思います。

1点目は、元々の型のプロパティとPick型のプロパティにスペルミスがあるとエラーになります。

2点目は、元々の型がreadonlyで定義されていた場合は、設定が引き継がれます。

3点目は、元々の型と新しい型は別物なので、

元々の型に新しい型のオブジェクトを入れることはできません。

以上の3点に注意して使うと、可読性や保守性が上がります。

条件型(conditional type)

次は、条件型(conditional type)について解説します。

条件型は、簡単にいうと条件によって型が変化する型になります。

条件型は、以下のように定義されます。

T extends U ? X : Y

これはTがUのサブタイプである場合はX型になり、

サブタイプではない場合はY型になるという意味です。

文字だけだと使い方も使い所も分かりづらいと思いますので、

以下のサンプルコードと一緒に詳しく解説します。

サンプルコード

ここでは、サンプルコードとして、基本的なものから少し高度なものをいくつか紹介します。

基本的な使い方

基本的な使い方としては、以下のようになります。

まずは、条件型の定義をした上で、条件ごとに任意の値を設定しています。

注目するポイントとしては、条件型の定義の際に、

条件によってオブジェクトのプロパティが変化している点です。

このように、条件型は条件によって型を自由に変更させることができます。

// 条件型の定義
type IsString<T> = T extends string ? { check: true } : { check: false, message: string };

// 条件別に値を設定
const a: IsString<string> = { check: true };
const b: IsString<number> = { check: false, message: 'number型です' };

// コンソール出力
console.log(a); // { check: true }
console.log(b); // { check: false, message: 'number型です' }

実践的な使い方

次は実践的な使い方のサンプルコードを紹介します。

今回の例では、より実践的にするためにAPIから返ってきたデータを想定して作成しました。

まずは、条件型の定義を行った後、

成功か失敗かの条件によって使用する型を変更する関数を挟んでいます。

// 条件型の判定がtrueの場合に使用する型の定義
type Data = {
    name: string
    age: number
}

// 条件型の定義
type ApiResponse<T> = T extends 'success'
    ? { data: Data }
    : { error: string };

// データ格納用の関数
function handleResponse<T extends 'success' | 'error'>(type: T): ApiResponse<T> {
    if (type === "success") {
        return {
            data: {
                name: '工場長',
                age: 24,
            }
        } as ApiResponse<T>;
    } else {
        return { error: 'エラーが発生しました。' } as ApiResponse<T>;
    }
}

// データ格納用関数の結果を変数に格納
const successResponse = handleResponse('success');
const errorResponse = handleResponse('error');

// コンソール出力
console.log(successResponse.data); // { name: '工場長', age: 24 }
console.log(errorResponse.error); // エラーが発生しました。

メリットと注意点

条件型には、もちろんメリットと注意しておく点がいくつか存在します。

このメリットと注意点を押さえておけば、非常に便利な型としてプログラムに使用できます。

メリット

条件型を使用するメリットは、2点あります。

1点目は、型の柔軟性が上がる点です。

柔軟性とは、条件によって使用する型を自在に変化させることが可能なので、

複雑な型操作を割とシンプルに表現することができます。

2点目は、再利用性の高さです。

条件によって、型を変化させることが可能ということは、より汎用的になるという意味になります。

そのため、サンプルコードで紹介したようにAPIからのレスポンスを統一させることも可能になります。

注意点

注意点は、2点あります。

1点目は、条件型を多用すると複雑になってしまう点です。

使いすぎると、どんどん複雑化して、可読性の低下につながります。

ですので、必要な場面を見極めることが大切です。

2点目は、デバックの難度が上がる点です。

1点目の注意点とつながりますが、可読性が低下してしまった場合に、

データの流れを追うのが難しくなります。

まとめ

ここまで読んでいただきありがとうございます。

今回は、TypeScriptの高度な型についていくつか解説しました。

それぞれ、とても便利ですが注意点を把握せずに使用すると思わぬ不具合に

つながる可能性もありますので、注意点を把握した上で使用してみてください。

サンプルコードを拡張して色々試してみると良いと思います。

TypeScriptには、今回紹介したもの以外にも便利な型や機能がたくさんあります。

他の記事でもTypeScriptの解説をしていますので、一緒に読んでもらえると嬉しいです。

コメント

タイトルとURLをコピーしました