TypeScriptでカスタムタイプを作成する方法
著者はCOVID-19救済基金を選択し、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
TypeScript は、 JavaScript 言語の拡張であり、コンパイル時の型チェッカーでJavaScriptのランタイムを使用します。 この組み合わせにより、開発者は完全なJavaScriptエコシステムと言語機能を使用できると同時に、オプションの静的型チェック、列挙型、クラス、およびインターフェースをその上に追加できます。
TypeScript の既成の基本型は多くのユースケースをカバーしますが、これらの基本型に基づいて独自のカスタム型を作成すると、型チェッカーがプロジェクトに固有のデータ構造を検証できるようになります。 これにより、プロジェクトでバグが発生する可能性が低くなり、コード全体で使用されるデータ構造のドキュメントが改善されます。
このチュートリアルでは、TypeScriptでカスタム型を使用する方法、それらの型を和集合や交差点と一緒に作成する方法、およびユーティリティ型を使用してカスタム型に柔軟性を追加する方法を示します。 さまざまなコードサンプルを紹介します。これらのサンプルは、独自のTypeScript環境、またはブラウザで直接TypeScriptを記述できるオンライン環境である TypeScriptPlaygroundで実行できます。
前提条件
このチュートリアルに従うには、次のものが必要です。
- TypeScriptプログラムを実行して、例に従うことができる環境。 これをローカルマシンに設定するには、次のものが必要です。 TypeScript関連のパッケージを処理する開発環境を実行するためにインストールされたNodeとnpm(またはyarn)の両方。 このチュートリアルは、Node.jsバージョン14.3.0およびnpmバージョン6.14.5でテストされました。 macOSまたはUbuntu18.04にインストールするには、「Node.jsをインストールしてmacOSにローカル開発環境を作成する方法」または「Ubuntu18.04にNode.jsをインストールする方法」の「PPAを使用したインストール」セクションの手順に従います。 これは、Windows Subsystem for Linux(WSL)を使用している場合にも機能します。 さらに、TypeScriptコンパイラ(tsc)がマシンにインストールされている必要があります。 これを行うには、TypeScriptの公式Webサイトを参照してください。
- ローカルマシン上にTypeScript環境を作成したくない場合は、公式の TypeScriptPlaygroundを使用してフォローできます。
- JavaScript、特に destructuring、REST演算子、 imports /exportsなどのES6+構文に関する十分な知識が必要です。 これらのトピックに関する詳細情報が必要な場合は、JavaScriptシリーズのコーディング方法を読むことをお勧めします。
- このチュートリアルでは、TypeScriptをサポートし、インラインエラーを表示するテキストエディタの側面を参照します。 これはTypeScriptを使用するために必要ではありませんが、TypeScript機能をさらに活用します。 これらの利点を活用するには、 Visual Studio Code のようなテキストエディターを使用できます。このエディターは、そのままTypeScriptを完全にサポートしています。 TypeScriptPlaygroundでこれらの利点を試すこともできます。
このチュートリアルに示されているすべての例は、TypeScriptバージョン4.2.2を使用して作成されています。
カスタムタイプの作成
プログラムが複雑なデータ構造を持っている場合、TypeScriptの基本型を使用しても、使用しているデータ構造を完全に記述できない場合があります。 このような場合、独自のタイプを宣言すると、複雑さに対処するのに役立ちます。 このセクションでは、コードで使用する必要のあるオブジェクトの形状を記述するために使用できるタイプを作成します。
カスタムタイプ構文
TypeScriptでは、カスタムタイプを作成するための構文は、typeキーワードに続いてタイプ名を使用し、次にタイププロパティを使用して{}ブロックに割り当てることです。 次のようにします。
type Programmer = {
name: string;
knownFor: string[];
};
構文はオブジェクトリテラルに似ています。ここで、キーはプロパティの名前であり、値はこのプロパティが持つべきタイプです。 これは、文字列値を保持するnameキーと文字列の配列を保持するknownForキーを持つオブジェクトでなければならないタイプProgrammerを定義します。
前の例で示したように、;を各プロパティ間の区切り文字として使用できます。 次に示すように、コンマ,を使用したり、区切り文字を完全に省略したりすることもできます。
type Programmer = {
name: string
knownFor: string[]
};
カスタムタイプを使用することは、基本タイプを使用することと同じです。 二重コロンを追加してから、タイプ名を追加します。
type Programmer = {
name: string;
knownFor: string[];
};
const ada: Programmer = {
name: 'Ada Lovelace',
knownFor: ['Mathematics', 'Computing', 'First Programmer']
};
ada定数は、エラーをスローせずにタイプチェッカーに合格するようになりました。
TypeScript Playgroundのように、TypeScriptを完全にサポートするエディターでこの例を作成すると、次のアニメーションに示すように、エディターはそのオブジェクトが期待するフィールドとそのタイプを提案します。
TypeScriptコメントドキュメントの一般的なスタイルであるTSDoc形式を使用してフィールドにコメントを追加する場合は、コード補完でも提案されます。 コメントに説明を付けて次のコードを取得します。
type Programmer = {
/**
* The full name of the Programmer
*/
name: string;
/**
* This Programmer is known for what?
*/
knownFor: string[];
};
const ada: Programmer = {
name: 'Ada Lovelace',
knownFor: ['Mathematics', 'Computing', 'First Programmer']
};
コメントされた説明は、フィールドの提案とともに表示されます。
カスタムタイプProgrammerでオブジェクトを作成するときに、予期しないタイプの値をいずれかのプロパティに割り当てると、TypeScriptはエラーをスローします。 次のコードブロックを、型宣言に準拠していない強調表示された行とともに取得します。
type Programmer = {
name: string;
knownFor: string[];
};
const ada: Programmer = {
name: true,
knownFor: ['Mathematics', 'Computing', 'First Programmer']
};
TypeScriptコンパイラ(tsc)は、エラー2322を表示します。
OutputType 'boolean' is not assignable to type 'string'. (2322)
次のように、タイプに必要なプロパティのいずれかを省略した場合:
type Programmer = {
name: string;
knownFor: string[];
};
const ada: Programmer = {
name: 'Ada Lovelace'
};
TypeScriptコンパイラはエラー2741を出します:
OutputProperty 'knownFor' is missing in type '{ name: string; }' but required in type 'Programmer'. (2741)
元のタイプで指定されていない新しいプロパティを追加すると、エラーが発生します。
type Programmer = {
name: string;
knownFor: string[];
};
const ada: Programmer = {
name: "Ada Lovelace",
knownFor: ['Mathematics', 'Computing', 'First Programmer'],
age: 36
};
この場合、表示されるエラーは2322です。
OutputType '{ name: string; knownFor: string[]; age: number; }' is not assignable to type 'Programmer'.
Object literal may only specify known properties, and 'age' does not exist in type 'Programmer'.(2322)
ネストされたカスタムタイプ
カスタムタイプを一緒にネストすることもできます。 Personタイプに準拠するmanagerフィールドを持つCompanyタイプがあるとします。 次のようなタイプを作成できます。
type Person = {
name: string;
};
type Company = {
name: string;
manager: Person;
};
次に、次のようにタイプCompanyの値を作成できます。
const manager: Person = {
name: 'John Doe',
}
const company: Company = {
name: 'ACME',
manager,
}
manager定数は、managerフィールドに指定された型に適合するため、このコードは型チェッカーに合格します。 これは、オブジェクトプロパティの省略形を使用してmanagerを宣言することに注意してください。
manager定数は、Personタイプと同じ形状であるため、省略できます。 TypeScriptは、Personを持つように明示的に設定されていなくても、managerプロパティのタイプによって期待されるものと同じ形状のオブジェクトを使用すると、エラーを発生させません。タイプ
以下はエラーをスローしません:
const manager = {
name: 'John Doe'
}
const company: Company = {
name: 'ACME',
manager
}
さらに一歩進んで、managerをこのcompanyオブジェクトリテラル内に直接設定することもできます。
const company: Company = {
name: 'ACME',
manager: {
name: 'John Doe'
}
};
これらのシナリオはすべて有効です。
TypeScriptをサポートするエディターでこれらの例を書く場合、エディターは利用可能なタイプ情報を使用してそれ自体を文書化することがわかります。 前の例では、managerの{}オブジェクトリテラルを開くとすぐに、エディターはタイプstringのnameプロパティを期待します。
プロパティの数が固定された独自のカスタムタイプを作成するいくつかの例を実行したので、次に、タイプにオプションのプロパティを追加してみます。
オプションのプロパティ
前のセクションのカスタムタイプ宣言では、そのタイプで値を作成するときにプロパティを省略できません。 ただし、値の有無にかかわらずタイプチェッカーを渡すことができるオプションのプロパティが必要な場合があります。 このセクションでは、これらのオプションのプロパティを宣言します。
タイプにオプションのプロパティを追加するには、プロパティに?修飾子を追加します。 前のセクションのProgrammerタイプを使用して、次の強調表示された文字を追加して、knownForプロパティをオプションのプロパティに変更します。
type Programmer = {
name: string;
knownFor?: string[];
};
ここでは、プロパティ名の後に?修飾子を追加しています。 これにより、TypeScriptはこのプロパティをオプションと見なし、そのプロパティを省略してもエラーが発生しなくなります。
type Programmer = {
name: string;
knownFor?: string[];
};
const ada: Programmer = {
name: 'Ada Lovelace'
};
これはエラーなしで通過します。
型にオプションのプロパティを追加する方法がわかったので、次は、無制限の数のフィールドを保持できる型を作成する方法を学びます。
インデックス可能なタイプ
前の例では、特定のタイプが宣言されたときにそれらのプロパティを指定していない場合、そのタイプの値にプロパティを追加できないことを示しました。 このセクションでは、インデックス可能なタイプを作成します。これは、タイプのインデックス署名に従う場合に任意の数のフィールドを許可するタイプです。
Dataタイプがあり、anyタイプのプロパティを無制限に保持しているとします。 このタイプは次のように宣言できます。
type Data = {
[key: string]: any;
};
ここでは、中括弧({})で囲まれた型定義ブロックを使用して通常の型を作成し、[key: typeOfKeys]: typeOfValuesの形式で特別なプロパティを追加します。ここで、typeOfKeysは型です。そのオブジェクトのキーが持つべきであり、typeOfValuesはそれらのキーの値が持つべきタイプです。
その後、他のタイプと同じように通常どおりに使用できます。
type Data = {
[key: string]: any;
};
const someData: Data = {
someBooleanKey: true,
someStringKey: 'text goes here'
// ...
}
インデックス可能なタイプを使用すると、インデックスシグネチャと一致する限り、無制限の数のプロパティを割り当てることができます。これは、インデックス可能なタイプのキーと値のタイプを説明するために使用される名前です。 この場合、キーはstringタイプであり、値はanyタイプです。
通常のタイプの場合と同様に、インデックス可能なタイプに常に必要な特定のプロパティを追加することもできます。 次の強調表示されたコードでは、statusプロパティをDataタイプに追加しています。
type Data = {
status: boolean;
[key: string]: any;
};
const someData: Data = {
status: true,
someBooleanKey: true,
someStringKey: 'text goes here'
// ...
}
これは、Dataタイプオブジェクトには、タイプチェッカーに合格するためにboolean値を持つstatusキーが必要であることを意味します。
さまざまな数の要素を持つオブジェクトを作成できるようになったので、TypeScriptで配列について学習することができます。これには、カスタム数以上の要素を含めることができます。
要素数以上の配列を作成する
TypeScriptで利用可能な配列とタプル基本型の両方を使用して、最小量の要素を持つ必要がある配列のカスタム型を作成できます。 このセクションでは、TypeScriptREST演算子...を使用してこれを行います。
複数の文字列をマージする関数があると想像してください。 この関数は、単一の配列パラメーターを取ります。 この配列には少なくとも2つの要素が必要であり、各要素は文字列である必要があります。 次のように、次のようなタイプを作成できます。
type MergeStringsArray = [string, string, ...string[]];
MergeStringsArray型は、配列型でrest演算子を使用できるという事実を利用しており、その結果をタプルの3番目の要素として使用します。 つまり、最初の2つの文字列が必要ですが、その後に追加の文字列要素は必要ありません。
配列に含まれる文字列要素が2つ未満の場合、次のように無効になります。
const invalidArray: MergeStringsArray = ['some-string']
TypeScriptコンパイラは、この配列をチェックするときにエラー2322を出します。
OutputType '[string]' is not assignable to type 'MergeStringsArray'. Source has 1 element(s) but target requires 2. (2322)
これまで、基本タイプの組み合わせから独自のカスタムタイプを作成してきました。 次のセクションでは、2つ以上のカスタムタイプを一緒に作成して、新しいタイプを作成します。
構成タイプ
このセクションでは、タイプを一緒に作成する2つの方法について説明します。 これらは、ユニオン演算子を使用していずれかのタイプに準拠するデータを渡し、交差演算子を使用して両方のタイプのすべての条件を満たすデータを渡します。
組合
ユニオンは、|(パイプ)演算子を使用して作成されます。この演算子は、ユニオン内の任意のタイプを持つことができる値を表します。 次の例を見てください。
type ProductCode = number | string
このコードでは、ProductCodeはstringまたはnumberのいずれかになります。 次のコードはタイプチェッカーに合格します。
type ProductCode = number | string; const productCodeA: ProductCode = 'this-works'; const productCodeB: ProductCode = 1024;
共用体型は、任意の有効なTypeScript型の共用体から作成できます。
交差点
交差タイプを使用して、交差するすべてのタイプのすべてのプロパティを持つ完全に新しいタイプを作成できます。
たとえば、API呼び出しの応答に常に表示されるいくつかの一般的なフィールドがあり、次にいくつかのエンドポイントの特定のフィールドがあるとします。
type StatusResponse = {
status: number;
isValid: boolean;
};
type User = {
name: string;
};
type GetUserResponse = {
user: User;
};
この場合、すべての応答にはstatusおよびisValidプロパティがありますが、追加のuserフィールドを持つのはユーザーの応答のみです。 交差タイプを使用して特定のAPIユーザー呼び出しの結果の応答を作成するには、StatusResponseタイプとGetUserResponseタイプの両方を組み合わせます。
type ApiGetUserResponse = StatusResponse & GetUserResponse;
タイプApiGetUserResponseには、StatusResponseで使用可能なすべてのプロパティとGetUserResponseで使用可能なプロパティがあります。 これは、データが両方のタイプのすべての条件を満たす場合にのみ、データがタイプチェッカーを通過することを意味します。 次の例が機能します。
let response: ApiGetUserResponse = {
status: 200,
isValid: true,
user: {
name: 'Sammy'
}
}
別の例は、結合を含むクエリに対してデータベースクライアントによって返される行のタイプです。 交差型を使用して、このようなクエリの結果を指定できます。
type UserRoleRow = {
role: string;
}
type UserRow = {
name: string;
};
type UserWithRoleRow = UserRow & UserRoleRow;
後で、次のようなfetchRowsFromDatabase()関数を使用した場合:
const joinedRows: UserWithRoleRow = fetchRowsFromDatabase()
結果の定数joinedRowsには、タイプチェッカーに合格するために両方とも文字列値を保持するroleプロパティとnameプロパティが必要です。
テンプレート文字列タイプの使用
TypeScript 4.1以降、 templatestringタイプを使用してタイプを作成できるようになりました。 これにより、特定の文字列形式をチェックする型を作成し、TypeScriptプロジェクトにさらにカスタマイズを加えることができます。
テンプレート文字列型を作成するには、テンプレート文字列リテラルを作成するときに使用する構文とほぼ同じ構文を使用します。 ただし、値の代わりに、文字列テンプレート内で他のタイプを使用します。
getで始まるすべての文字列を渡す型を作成したいとします。 テンプレート文字列タイプを使用してこれを行うことができます。
type StringThatStartsWithGet = `get${string}`;
const myString: StringThatStartsWithGet = 'getAbc';
文字列はgetで始まり、その後に追加の文字列が続くため、myStringはここでタイプチェッカーに合格します。
次のinvalidStringValueのように、タイプに無効な値を渡した場合:
type StringThatStartsWithGet = `get${string}`;
const invalidStringValue: StringThatStartsWithGet = 'something';
TypeScriptコンパイラはエラー2322を出します:
OutputType '"something"' is not assignable to type '`get${string}`'. (2322)
テンプレート文字列を使用して型を作成すると、プロジェクトの特定のニーズに合わせて型をカスタマイズするのに役立ちます。 次のセクションでは、型アサーションを試してみます。型アサーションは、型指定されていないデータに型を追加します。
タイプアサーションの使用
any type は、任意の値の型として使用できますが、TypeScriptを最大限に活用するために必要な強い型付けを提供しないことがよくあります。 ただし、anyにバインドされた変数が制御できない場合があります。 これは、TypeScriptで記述されていない、またはタイプ宣言が利用できない外部依存関係を使用している場合に発生します。
これらのシナリオでコードをタイプセーフにしたい場合は、タイプアサーションを使用できます。これは、変数のタイプを別のタイプに変更する方法です。 型アサーションは、変数の後にas NewTypeを追加することで可能になります。 これにより、変数のタイプがasキーワードの後に指定されたタイプに変更されます。
次の例を見てください。
const valueA: any = 'something'; const valueB = valueA as string;
valueAのタイプはanyですが、asキーワードを使用すると、このコードはvalueBのタイプをstringに強制します。
注: TypeAの変数をTypeBタイプにアサートするには、TypeBはTypeAのサブタイプである必要があります。 neverを除くほとんどすべてのTypeScriptタイプは、unknownを含むanyのサブタイプです。
ユーティリティタイプ
前のセクションでは、基本タイプからカスタムタイプを作成する複数の方法を確認しました。 ただし、まったく新しいタイプを最初から作成したくない場合もあります。 既存のタイプのいくつかのプロパティを使用したり、別のタイプと同じ形状であるがすべてのプロパティがオプションに設定されている新しいタイプを作成したりするのが最適な場合があります。
これはすべて、TypeScriptで利用可能な既存のユーティリティタイプを使用して可能です。 このセクションでは、これらのユーティリティタイプのいくつかについて説明します。 利用可能なすべてのものの完全なリストについては、TypeScriptハンドブックのユーティリティタイプの部分を参照してください。
すべてのユーティリティタイプは汎用タイプであり、他のタイプをパラメーターとして受け入れるタイプと考えることができます。 ジェネリック型は、<TypeA, TypeB, ...>構文を使用して型パラメーターを渡すことができることで識別できます。
Record<Key, Value>
Recordユーティリティタイプを使用すると、前に説明したインデックス署名を使用するよりもクリーンな方法でインデックス可能なタイプを作成できます。
インデックス可能なタイプの例では、次のタイプがあります。
type Data = {
[key: string]: any;
};
次のようなインデックス可能なタイプの代わりに、Recordユーティリティタイプを使用できます。
type Data = Record<string, any>;
Recordジェネリックの最初の型パラメーターは、各keyの型です。 次の例では、すべてのキーが文字列である必要があります。
type Data = Record<string, any>
2番目のタイプパラメータは、これらのキーの各valueのタイプです。 次の場合、値はanyになります。
type Data = Record<string, any>
Omit<Type, Fields>
Omitユーティリティタイプは、別のタイプに基づいて新しいタイプを作成するのに役立ちますが、結果のタイプに不要ないくつかのプロパティを除外します。
データベース内のユーザー行のタイプを表す次のタイプがあるとします。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
コードでaddressId以外のすべてのフィールドを取得している場合は、Omitを使用して、そのフィールドのない新しいタイプを作成できます。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
type UserRowWithoutAddressId = Omit<UserRow, 'addressId'>;
Omitの最初の引数は、新しい型の基になる型です。 2つ目は、省略したいフィールドです。
コードエディタでUserRowWithoutAddressIdにカーソルを合わせると、UserRowタイプのすべてのプロパティがありますが、省略したプロパティがあります。
文字列の和集合を使用して、2番目のタイプパラメータに複数のフィールドを渡すことができます。 idフィールドも省略したい場合は、次のようにします。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
type UserRowWithoutIds = Omit<UserRow, 'id' | 'addressId'>;
Pick<Type, Fields>
Pickユーティリティタイプは、Omitタイプの正反対です。 省略したいフィールドを言う代わりに、別のタイプから使用したいフィールドを指定します。
以前に使用したものと同じUserRowを使用します。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
データベース行からemailキーのみを選択する必要があると想像してください。 このようなタイプは、Pickを使用して次のように作成できます。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
type UserRowWithEmailOnly = Pick<UserRow, 'email'>;
ここでのPickの最初の引数は、新しいタイプの基になるタイプを指定します。 2つ目は、含めたいキーです。
これは、次のようになります。
type UserRowWithEmailOnly = {
email: string;
}
文字列の和集合を使用して、複数のフィールドを選択することもできます。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
type UserRowWithEmailOnly = Pick<UserRow, 'name' | 'email'>;
Partial<Type>
同じUserRowの例を使用して、データベースクライアントがユーザーテーブルに新しいデータを挿入するために使用できるオブジェクトに一致する新しいタイプを作成するとしますが、詳細は1つだけです。データベースにはすべてのデフォルト値がありますフィールドなので、それらのいずれかを渡す必要はありません。 これを行うには、Partialユーティリティタイプを使用して、オプションで基本タイプのすべてのフィールドを含めることができます。
既存のタイプUserRowには、必要に応じてすべてのプロパティがあります。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
すべてのプロパティがオプションである新しいタイプを作成するには、次のようなPartial<Type>ユーティリティタイプを使用できます。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
type UserRowInsert = Partial<UserRow>;
これは、UserRowInsertを次のようにするのとまったく同じです。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
type UserRowInsert = {
id?: number | undefined;
name?: string | undefined;
email?: string | undefined;
addressId?: string | undefined;
};
ユーティリティタイプは、TypeScriptの基本タイプからタイプを作成するよりも高速にタイプを構築する方法を提供するため、持つのに最適なリソースです。
結論
独自のコードで使用されるデータ構造を表す独自のカスタムタイプを作成すると、プロジェクトに柔軟で便利なTypeScriptソリューションを提供できます。 独自のコード全体の型安全性を高めることに加えて、独自のビジネスオブジェクトをコード内のデータ構造として型指定すると、コードベースの全体的なドキュメントが増え、チームメートと協力して開発者のエクスペリエンスが向上します。同じコードベース。
TypeScriptのその他のチュートリアルについては、TypeScriptシリーズのコーディング方法ページをご覧ください。