TypeScriptは、JavaScriptに型安全性を加えることで、より堅牢なコードを作成するための強力なツールです。特に、nullやundefinedといった値は、予期せぬバグを引き起こす原因となるため、これらを許容しない型定義を行うことが重要です。TypeScriptでは、never型を活用することで、これらの問題を未然に防ぎ、より安全なコードを実現することができます。本記事では、nullやundefinedを許容しない型定義の方法と、never型の効果的な使い方について詳しく解説します。
TypeScriptにおけるnullとundefined
TypeScriptでは、nullとundefinedは非常に注意が必要な値です。JavaScriptの仕様上、どちらも値が存在しないことを表しますが、異なる意味を持ちます。nullは「意図的に値が空である」ことを示し、undefinedは「値が未定義である」ことを意味します。
TypeScriptでのnullとundefinedの扱い
TypeScriptはstrictNullChecks
というオプションを用いることで、nullやundefinedを明示的に許可しない型安全なコードを書くことができます。このオプションを有効にすると、nullやundefinedが許可される場面が制限され、未定義な値の誤使用によるバグが減少します。
let value: string = "Hello";
// 以下のような代入はstrictNullChecksが有効だとエラー
value = null; // Error: Type 'null' is not assignable to type 'string'
nullやundefinedの問題点
nullやundefinedを許容すると、次のような問題が発生することがあります。
- 予期せぬバグ: 値が存在しない状態を見逃し、コードが予期せぬ動作をする。
- エラーハンドリングの煩雑化: nullやundefinedに対するチェックが必要になり、コードが複雑になる。
これらの問題を避けるために、nullやundefinedを許容しない型定義を行うことがTypeScriptでは推奨されます。
never型とは
TypeScriptにおけるnever型は、決して値を返さないことを示す特殊な型です。これは、関数が正常に終了しない場合や無限ループが発生する場合に使用されます。具体的には、例外をスローする関数や、到達不能なコードに対して使用されることが多いです。
never型の特徴
never型は、次のような状況で使われます。
- 例外をスローする関数: 常にエラーを発生させ、正常に処理を終了しない関数に使います。
- 到達不能なコード: 型推論や型チェックの結果、通常の実行フローに到達しないコード部分で使用されます。
- スイッチ文の網羅性チェック: TypeScriptの型システムが、条件分岐やスイッチ文ですべてのケースをカバーしていないことを検出するのに使われます。
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}
never型が使われるケース
never型は、型安全性を高め、コードの予期しない動作を防ぐために非常に有効です。たとえば、スイッチ文で全てのケースが網羅されていない場合、never型を利用してエラーが発生する箇所を明示的に示すことができます。
type Value = "A" | "B";
function checkValue(value: Value) {
switch (value) {
case "A":
return "Value is A";
case "B":
return "Value is B";
default:
// ここでnever型を活用し、未対応のケースを検出
const exhaustiveCheck: never = value;
throw new Error(`Unhandled case: ${exhaustiveCheck}`);
}
}
このようにnever型を使用することで、予期しないケースが発生した場合にエラーを明示的に発生させることができ、堅牢なコードを書くことができます。
never型の基本的な使い方
never型は、関数やコードブロックが値を返さず、正常に完了しないことを示す際に使用されます。この型は、他のどの型とも交差せず、決して代入可能な値が存在しないという特性を持っています。ここでは、never型の具体的な使い方について見ていきます。
例1: エラーハンドリングにおけるnever型
never型の典型的な使い方の1つは、例外をスローする関数に使用することです。こうした関数は常にエラーを発生させ、実際には値を返すことがないため、戻り値の型としてneverを指定します。
function throwError(message: string): never {
throw new Error(message);
}
この場合、throwError
関数はエラーをスローするため、戻り値として何も返しません。したがって、戻り値の型はnever
になります。
例2: 無限ループにおけるnever型
never型は、無限ループを含む関数にも使われます。このような関数は永久に終了せず、戻り値が存在しないため、戻り値の型としてneverが適切です。
function infiniteLoop(): never {
while (true) {
// ここは永久に実行され続けます
}
}
この関数も正常に終了しないため、never
型を指定することができます。
例3: 型安全性を向上させるnever型
never型は、条件分岐やスイッチ文において型の網羅性を保証するためにも使われます。たとえば、次のような関数では、全てのケースをカバーしないとコンパイル時にエラーが発生します。
type Value = "A" | "B";
function handleValue(value: Value): string {
switch (value) {
case "A":
return "You selected A";
case "B":
return "You selected B";
default:
const exhaustiveCheck: never = value;
throw new Error(`Unhandled case: ${exhaustiveCheck}`);
}
}
この例では、Value
型のすべての可能なケースをカバーしない場合、TypeScriptがエラーを発生させ、見逃しを防ぎます。never
型を使うことで、このような網羅性チェックを行い、型安全性を向上させることができます。
基本的なポイント
- never型は、決して値を返さない関数やコードに使う。
- 例外スローや無限ループ、網羅性チェックに効果的に利用される。
- 型安全性を確保し、予期しないバグを防ぐのに役立つ。
このように、never型を適切に利用することで、コードの堅牢性と安全性を高めることができます。
nullやundefinedを許容しない型定義の方法
TypeScriptでは、nullやundefinedを許容しない型定義を行うことで、予期せぬバグやエラーを未然に防ぐことができます。これにより、より安全で堅牢なコードを記述することが可能になります。ここでは、nullやundefinedを許容しない型定義の具体的な方法について解説します。
strictNullChecksオプションを利用する
TypeScriptコンパイラには、strictNullChecks
というオプションがあります。このオプションを有効にすることで、nullやundefinedが他の型と区別され、明示的に扱わない限り、それらの値を代入できなくなります。これは、nullやundefinedによる実行時エラーを防ぐ強力な方法です。
tsconfig.jsonでstrictNullChecks
を有効にする方法:
{
"compilerOptions": {
"strictNullChecks": true
}
}
この設定により、nullやundefinedが型の一部として明示的に指定されない限り、変数にnullやundefinedを代入することができなくなります。
Union型でnullやundefinedを明示的に許可しない
strictNullChecks
を有効にした状態で、特定の変数やプロパティがnullやundefinedを持つ可能性がある場合は、Union型(共用体型)を用いてそれらを明示的に型定義することができます。
let value: string | null = "Hello";
value = null; // 許容される
value = undefined; // Error: Type 'undefined' is not assignable to type 'string | null'
この例では、string | null
というUnion型が使用されており、nullは許可されていますが、undefinedは許可されていないためエラーとなります。
Optional chainingとnullish coalescing演算子
nullやundefinedを許容しないコードを書く際に、TypeScriptのoptional chaining(オプショナルチェーン)やnullish coalescing(ヌリッシュコアレッシング)演算子を利用すると、コードの可読性を向上させつつ、安全にnullやundefinedを扱うことができます。
const user = {
name: "John",
address: null
};
// オプショナルチェーンでnullやundefinedを許容しない
const city = user?.address?.city ?? "Unknown city";
console.log(city); // "Unknown city"
この例では、user?.address?.city
とすることで、address
やcity
がnullまたはundefinedの場合でも安全に処理を行うことができます。
型ガードでnullやundefinedを回避
TypeScriptの型ガードを使うことで、nullやundefinedを明示的に回避することができます。特に、関数や条件文内でのチェックが必要な場合に有効です。
function printLength(value: string | null): void {
if (value !== null) {
console.log(value.length);
} else {
console.log("Value is null");
}
}
このように、nullやundefinedを許容しない型定義を行うことで、型の安全性を確保し、予期せぬエラーを防ぐことができます。
never型を使ったエラーハンドリング
TypeScriptでは、エラーハンドリングにおいてもnever型が非常に有効です。エラーが発生した場合に、その後の処理が続かないことを明示的に示すために、never型を使用することで、コードの堅牢性と型安全性を高めることができます。ここでは、never型を活用したエラーハンドリングの方法について詳しく説明します。
例外処理とnever型
never型は、特にエラーをスローする関数でよく使われます。例外をスローする関数は通常の実行フローに戻ることがなく、必ずエラーで終了するため、戻り値の型はnever型になります。これにより、コンパイラが「この関数は絶対に終了しない」ことを認識し、型チェックを厳密に行うことができます。
function throwError(message: string): never {
throw new Error(message);
}
function processUserInput(input: string | null): string {
if (input === null) {
return throwError("Input cannot be null");
}
return input;
}
上記のコードでは、throwError
関数がエラーをスローして終了します。このように、戻り値が存在しない関数にnever型を適用することで、エラーハンドリングの処理が型安全に行われるようになります。
未処理のケースを検出するためのnever型
スイッチ文や条件分岐において、すべてのケースが網羅されていない場合、never型を使って未処理のケースを検出することができます。これにより、予期しないエラーを未然に防ぐことが可能になります。
type Response = "success" | "error";
function handleResponse(response: Response): string {
switch (response) {
case "success":
return "Operation succeeded";
case "error":
return "Operation failed";
default:
const exhaustiveCheck: never = response;
return exhaustiveCheck; // コンパイル時にエラー
}
}
この例では、Response
型の全てのケースをスイッチ文でカバーしているかどうかをコンパイラがチェックします。もし新たなケースが追加されていても処理が網羅されていない場合、exhaustiveCheck
の部分でコンパイル時にエラーが発生します。このようにnever型を使うことで、将来的に追加されるケースの見落としを防ぐことができます。
エラーハンドリングにおける型の安全性
never型をエラーハンドリングに適用することで、型の安全性を強化できます。例外をスローすることでコードのフローを強制的に停止し、正しい型チェックを行うことができるため、予期しない動作やバグを防ぐことができます。また、例外処理と条件分岐を組み合わせることで、堅牢なエラーハンドリングの実装が可能です。
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
function handleAction(action: "start" | "stop"): void {
switch (action) {
case "start":
console.log("Starting...");
break;
case "stop":
console.log("Stopping...");
break;
default:
assertNever(action); // ここでnever型を使って未処理のケースをチェック
}
}
このコードは、assertNever
関数を用いて、処理されていないアクションを検出します。これにより、コードに新しいアクションが追加された際に、全てのケースが正しく処理されているかを型レベルで確認できます。
まとめ
- never型は、エラーハンドリングにおいて特に有効で、予期しないコードの実行を防ぐために使われる。
- 例外をスローする関数やスイッチ文で網羅性をチェックする際に使用され、型安全性を高める。
- never型を活用することで、エラーハンドリングがより堅牢で信頼性の高いものになる。
このように、エラーハンドリングにnever型を適用することで、予期せぬエラーを未然に防ぎ、安全で効率的なコードを書くことができます。
実用例:関数の引数と返り値におけるnever型
TypeScriptのnever型は、関数の引数や返り値においても非常に役立ちます。特に、エラーハンドリングや予期しない値を許容しないように設計する際に、never型を活用することで、型安全性をさらに高めることができます。ここでは、never型を関数の引数や返り値でどのように使用するかを具体的な例で説明します。
関数の引数でのnever型の使用
関数の引数にnever型を指定することで、実際にはその引数が使われることがないことを明示的に示すことができます。通常、関数の引数にnever型を使用する場合、条件分岐や型チェックによって型が狭められた結果としてnever型が割り当てられることが多いです。
次の例では、関数に渡される型が想定外のものであった場合に、never型を使用してそのエラーを処理しています。
type Action = "start" | "stop";
function handleAction(action: Action): void {
switch (action) {
case "start":
console.log("Action: start");
break;
case "stop":
console.log("Action: stop");
break;
default:
const _exhaustiveCheck: never = action;
throw new Error(`Unhandled action: ${action}`);
}
}
この例では、Action
型のすべてのケースを処理していますが、将来新しいアクションが追加されてスイッチ文でカバーされていない場合、default
ケースでエラーが発生し、そのケースが型安全に検出されます。これにより、想定外のアクションが渡された際に型レベルでエラーを確認でき、バグを防ぐことができます。
関数の返り値でのnever型の使用
関数の返り値にnever型を指定する場合、その関数は決して値を返さない、または正常に終了しないことを意味します。これは、エラーをスローする場合や、無限ループが存在する場合に特に役立ちます。
次の例では、必ずエラーをスローする関数の返り値としてnever型が使用されています。
function throwError(message: string): never {
throw new Error(message);
}
function processInput(input: string | null): string {
if (input === null) {
return throwError("Input cannot be null");
}
return input;
}
このコードでは、throwError
関数は常に例外をスローするため、返り値の型としてnever
を指定しています。processInput
関数内でnullが渡された場合、throwError
が呼び出され、プログラムがそこで停止することを明示的に示しています。
never型を活用した総合的なエラーハンドリング
関数の引数や返り値にnever型を使用することで、予期せぬエラーや未処理のケースを堅実に処理することができます。特に、条件分岐やスイッチ文などで網羅性を保証することができ、コードの安全性が大幅に向上します。
以下は、エラーハンドリングを伴う複雑な関数の例です。
type UserAction = "login" | "logout" | "register";
function performUserAction(action: UserAction): void {
switch (action) {
case "login":
console.log("User logged in");
break;
case "logout":
console.log("User logged out");
break;
case "register":
console.log("User registered");
break;
default:
// never型を活用して未対応のアクションを検出
const exhaustiveCheck: never = action;
throw new Error(`Unhandled action: ${exhaustiveCheck}`);
}
}
この例では、ユーザーアクションの処理をスイッチ文で行い、UserAction
型のすべての可能性をカバーしています。もし新しいアクションが追加され、スイッチ文でカバーされていない場合、never
型を使ったチェックでエラーが発生するため、開発者は新たなケースを見逃すことなく対処できます。
まとめ
- 関数の引数や返り値にnever型を使用することで、予期しない値やエラーを型レベルで検出することができる。
- never型は、未処理のケースを網羅するために特に有効で、スイッチ文や条件分岐のエラーハンドリングに役立つ。
- これにより、コードの安全性と信頼性が向上し、将来の拡張にも対応しやすくなる。
関数の引数や返り値でnever型を適切に使用することにより、より堅牢で型安全なコードを書くことができ、開発プロセス全体がスムーズに進行します。
応用:never型と条件付き型の組み合わせ
TypeScriptの条件付き型は、ある型に対して条件を設定し、動的に型を決定する強力な機能です。この機能とnever型を組み合わせることで、さらに柔軟で型安全なコードを書くことが可能になります。ここでは、never型と条件付き型を組み合わせた応用的な使い方について解説します。
条件付き型の基本
条件付き型は、次のような構文を持ちます。
T extends U ? X : Y
この型は、T
がU
に割り当て可能であればX
を、そうでなければY
を返す、という意味です。この機能を使うことで、型レベルで動的な判断ができるようになります。
never型を条件付き型で扱う
条件付き型とnever型を組み合わせることで、ある条件に基づいて型の範囲を狭めたり、処理しないケースを明示することができます。例えば、型がnever型の場合はその処理をスキップするなど、より柔軟な型定義が可能です。
次の例は、never型を条件付き型と組み合わせた例です。
type FilterNever<T> = T extends never ? never : T;
この条件付き型FilterNever
は、T
がnever型であればnever
を返し、そうでなければT
自身を返します。これにより、never型を除外した型を作成することができます。
実用例:Union型からnever型を除外する
Union型に対してnever型を条件付き型で除外するケースもよくあります。たとえば、あるUnion型からneverを除外して、実際に使える型だけを抽出することができます。
type NonNever<T> = T extends never ? never : T;
type Example = NonNever<"A" | "B" | never>; // "A" | "B"
この例では、Example
型は"A" | "B"
となり、never型は除外されています。このように、不要な型を取り除くことで、型定義がより明確になり、予期しないバグを防ぐことができます。
条件付き型を使ったnever型のエラーチェック
never型を使って型の網羅性をチェックする方法にも応用が効きます。たとえば、すべてのケースがカバーされていない場合にエラーをスローする機構を作ることができます。
type CheckExhaustive<T> = T extends never ? true : false;
type Action = "start" | "stop";
function assertExhaustive(action: Action): CheckExhaustive<Action> {
switch (action) {
case "start":
case "stop":
return false as CheckExhaustive<Action>;
default:
// ここで型レベルでエラーチェックが可能
throw new Error("Unhandled action");
}
}
この例では、CheckExhaustive
型を使用して、未処理のアクションがある場合にエラーを発生させる型チェックが行われています。もし新しいアクションが追加されていて、スイッチ文で対応していない場合は、never型が割り当てられるため、コンパイル時にエラーが検出されます。
never型と条件付き型の組み合わせで型安全性を向上
条件付き型とnever型の組み合わせは、次のような場面で特に役立ちます。
- 型のフィルタリング: 不要な型(never型など)をUnion型から除外する。
- エラーチェック: 未処理のケースや型の不一致を明示的に処理する。
- 動的な型推論: 特定の条件に基づいて型を変更することで、より柔軟なコードを実現。
type RemoveNullOrUndefined<T> = T extends null | undefined ? never : T;
type CleanedType = RemoveNullOrUndefined<string | null | undefined>; // string
この例では、RemoveNullOrUndefined
型がnullまたはundefinedを除外し、結果的にstring
型だけが残ります。このような型フィルタリングによって、よりクリーンな型定義が可能です。
まとめ
- never型と条件付き型を組み合わせることで、より柔軟で堅牢な型定義が可能になる。
- Union型からneverを除外したり、未処理のケースを型レベルでチェックするために使える。
- 条件付き型とnever型を活用することで、型安全性を高めつつ柔軟なコード設計を行える。
このように、条件付き型とnever型の組み合わせは、TypeScriptの型システムをさらに強化し、型安全なプログラムを実現するための非常に強力なツールです。
nullやundefinedの利用を最小限にするためのベストプラクティス
TypeScriptでは、nullやundefinedを許容しない型定義を行うことが重要です。これにより、実行時エラーや予期せぬバグを回避し、コードの堅牢性を高めることができます。ここでは、nullやundefinedの利用を最小限に抑え、安全なコードを書くためのベストプラクティスを紹介します。
1. strictNullChecksを有効にする
最初に行うべき設定は、TypeScriptのstrictNullChecks
オプションを有効にすることです。このオプションが有効になると、nullやundefinedが型システムで厳密に扱われ、予期しない値がコード中に混入するのを防ぎます。
{
"compilerOptions": {
"strictNullChecks": true
}
}
この設定により、nullやundefinedが型チェックの対象となり、これらの値を含む型を扱う際には明示的に指定する必要があります。これが堅牢なコードの第一歩です。
2. Optional chainingとnullish coalescingの活用
nullやundefinedを許容せずに安全に扱う方法として、Optional chaining(オプショナルチェーン)とnullish coalescing(ヌリッシュコアレッシング)を活用することが推奨されます。これにより、プロパティアクセス時にnullやundefinedを安全に扱え、コードの可読性が向上します。
const user = {
name: "John",
address: null
};
// オプショナルチェーンで安全にプロパティを参照
const city = user?.address?.city ?? "Unknown city";
console.log(city); // "Unknown city"
このコードでは、user?.address?.city
とすることで、address
がnullやundefinedの場合でも安全に処理を行うことができます。また、nullish coalescingを使って、nullやundefinedの場合にはデフォルト値を提供しています。
3. 明示的な型ガードを使用する
型ガードは、nullやundefinedを扱う際に非常に便利な機能です。型ガードを使用することで、これらの値を正しくチェックし、適切に処理することができます。
function processValue(value: string | null): string {
if (value !== null) {
return value;
}
return "Default value";
}
このように型ガードを用いてnullやundefinedをチェックすることで、コードが予期せぬ動作をするのを防ぐことができます。
4. nullやundefinedを含む型を避ける
可能な限り、nullやundefinedを含む型を使用せず、Union型やOptional型(?
)を使わない設計を心がけることも一つの方法です。例えば、関数の戻り値でnullを返すのではなく、結果を表す型を明示的に作る方法があります。
type Success<T> = { success: true; value: T };
type Failure = { success: false; error: string };
function getData(): Success<string> | Failure {
// 処理
if (/* エラー */) {
return { success: false, error: "Data not found" };
}
return { success: true, value: "Fetched data" };
}
この例では、結果をSuccess
とFailure
の2つの型で表現することで、nullやundefinedを避けつつ、明確なエラーハンドリングを行っています。
5. モナドやEither型の使用
nullやundefinedの代わりに、モナドやEither型を使って結果を表現することも考えられます。これにより、結果が存在するかどうかをより型安全に表現でき、コードの安全性が向上します。
例えば、Option
型を使用して、結果が存在する場合と存在しない場合を明示的に扱うことができます。
type Option<T> = T | null;
function findValue(): Option<string> {
// 値が存在する場合はstring、存在しない場合はnullを返す
return Math.random() > 0.5 ? "Found" : null;
}
const value = findValue();
if (value !== null) {
console.log("Value:", value);
} else {
console.log("No value found");
}
この方法により、nullを含む型を直接扱うことなく、データが存在しないケースを型レベルで明示的に扱うことができます。
まとめ
- strictNullChecksの有効化: nullやundefinedを厳密に扱う。
- Optional chainingとnullish coalescing: 安全なプロパティアクセスとデフォルト値を提供。
- 型ガードの活用: 明示的にnullやundefinedをチェック。
- nullやundefinedを含む型を避ける: 明示的な結果型を使用してエラーハンドリングを行う。
- モナドやOption型の利用: nullやundefinedの代替手段としてより安全な型を活用。
これらのベストプラクティスを実践することで、nullやundefinedによるバグを未然に防ぎ、より安全で信頼性の高いコードを実現できます。
演習問題:never型を活用したコード例
TypeScriptにおけるnever型の理解を深めるために、いくつかの実践的な演習問題を提供します。これらの演習を通じて、never型を適切に使い、型安全性を強化する方法を学びましょう。
演習1: 全てのケースをカバーするスイッチ文
次のコードは、Color
というUnion型を持つスイッチ文です。すべてのColor
値を正しく処理することが目的です。未処理のケースがあれば、コンパイルエラーが発生するように修正してください。
type Color = "red" | "green" | "blue";
function handleColor(color: Color): string {
switch (color) {
case "red":
return "Color is red";
case "green":
return "Color is green";
// ここで他のケースをカバーしてください
default:
// ここでnever型を使って未処理のケースを検出
const exhaustiveCheck: never = color;
return exhaustiveCheck;
}
}
解答のポイント:
blue
のケースを追加し、never
型で未処理のケースが存在しないことを保証します。
演習2: never型を返すエラーハンドリング関数
以下の関数handleError
は、エラーメッセージを受け取り例外をスローします。この関数が値を返さないことを明示するために、適切にnever型を使用してください。
function handleError(message: string): void {
// エラーメッセージをスロー
throw new Error(message);
}
修正点:
void
型をnever
型に変更し、この関数が決して値を返さないことを示す。
演習3: 型ガードを使ったnever型の活用
次の関数は、number
またはstring
を受け取り、それがnumber
なら数値の二乗を返し、string
なら文字列の長さを返します。未対応の型が渡された場合、never型を使って型エラーが発生するようにしてください。
function processValue(value: number | string): number {
if (typeof value === "number") {
return value * value;
} else if (typeof value === "string") {
return value.length;
} else {
// ここでnever型を使用し、型チェックを行う
const exhaustiveCheck: never = value;
throw new Error(`Unhandled type: ${exhaustiveCheck}`);
}
}
解答のポイント:
else
の分岐で、never型を使って未対応の型が渡された場合にエラーを発生させる。
演習4: never型を使った網羅的な型チェック
次のコードは、Shape
型を持つオブジェクトを処理します。それぞれのケースを正しくカバーし、未処理のケースが存在しないことを保証するためにnever型を使用して型安全性を保ちましょう。
type Shape = { kind: "circle"; radius: number } | { kind: "square"; sideLength: number };
function calculateArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius * shape.radius;
case "square":
return shape.sideLength * shape.sideLength;
// 未対応のケースをnever型でチェックしてください
}
}
解答のポイント:
- 未処理のケースに対して
default
でnever型を使い、型エラーを発生させるロジックを追加します。
まとめ
これらの演習を通じて、never型の活用方法について理解を深めていただけたと思います。never型は、型安全性を高め、予期せぬエラーを防ぐために重要なツールです。これらの問題を解くことで、より堅牢なTypeScriptコードを記述できるスキルを身につけることができるでしょう。
まとめ
本記事では、TypeScriptにおけるnullやundefinedを許容しない型定義と、never型の効果的な使い方について解説しました。nullやundefinedを扱う際のリスクを最小限に抑え、型安全なコードを書くためのベストプラクティスを学び、never型を活用することで、エラーハンドリングや条件分岐の網羅性を保証する方法も紹介しました。
これにより、予期せぬバグや型の不整合を未然に防ぎ、より堅牢で信頼性の高いTypeScriptコードを実現することができます。
コメント