型定義
TypeScriptの型定義:実践的な理解
Section titled “TypeScriptの型定義:実践的な理解”TypeScriptの型システムは、単なる型注釈ではなく、アプリケーションの設計と保守性を向上させる強力なツールです。この章では、各型の本質的な意味と実践的なユースケースを深く解説します。
なぜ型定義が重要なのか
Section titled “なぜ型定義が重要なのか”型安全性の本質的な価値
Section titled “型安全性の本質的な価値”問題のあるコード(JavaScript):
// 問題: 実行時までエラーが発見されないfunction calculateTotal(items) { return items.reduce((sum, item) => sum + item.price, 0);}
// 実行時にエラーが発生calculateTotal([{ price: 100 }, { price: "200" }]); // "100200" という文字列が返されるcalculateTotal(null); // TypeError: Cannot read property 'reduce' of null解決: TypeScriptの型定義
// 解決: コンパイル時にエラーを発見interface CartItem { price: number; name: string;}
function calculateTotal(items: CartItem[]): number { return items.reduce((sum, item) => sum + item.price, 0);}
// コンパイル時にエラーが検出されるcalculateTotal([{ price: 100 }, { price: "200" }]); // エラー: priceはnumber型である必要があるcalculateTotal(null); // エラー: nullは配列ではない型定義がもたらす価値:
- 早期のバグ発見: コンパイル時にエラーを検出
- 自己文書化: コードが型情報を含むため、ドキュメントとして機能
- IDE支援: 自動補完、リファクタリングの安全性
- チーム開発: インターフェースが明確になり、コミュニケーションが向上
1. プリミティブ型と基本型 🧱
Section titled “1. プリミティブ型と基本型 🧱”TypeScriptの型定義の基本となるものです。各型の実践的な使い方を理解することが重要です。
string: 文字列型
Section titled “string: 文字列型”基本的な使い方:
const name: string = "Alice";const message: string = `Hello, ${name}!`;実践的なユースケース:
// ユースケース1: APIレスポンスの型定義interface ApiResponse { status: string; // "success" | "error" message: string; data: unknown;}
// ユースケース2: リテラル型との組み合わせtype HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
function makeRequest(method: HttpMethod, url: string) { // methodは特定の文字列のみを受け入れる fetch(url, { method });}
makeRequest("GET", "/api/users"); // OKmakeRequest("PATCH", "/api/users"); // エラー: "PATCH"は許可されていないnumber: 数値型
Section titled “number: 数値型”基本的な使い方:
const age: number = 30;const price: number = 99.99;実践的なユースケース:
// ユースケース1: 金額の計算(精度が重要な場合)// 問題: 浮動小数点数の精度エラーconst total = 0.1 + 0.2; // 0.30000000000000004
// 解決: 整数として扱う(セント単位)type Cents = number; // セント単位の金額
function addCents(a: Cents, b: Cents): Cents { return a + b;}
const totalCents = addCents(10, 20); // 30セント
// ユースケース2: 範囲を制限する型type Percentage = number & { readonly __brand: unique symbol };
function createPercentage(value: number): Percentage { if (value < 0 || value > 100) { throw new Error("Percentage must be between 0 and 100"); } return value as Percentage;}
function applyDiscount(price: number, discount: Percentage): number { return price * (1 - discount / 100);}boolean: 真偽値型
Section titled “boolean: 真偽値型”基本的な使い方:
const isActive: boolean = true;const isCompleted: boolean = false;実践的なユースケース:
// ユースケース1: フラグの管理interface User { id: number; name: string; isActive: boolean; isEmailVerified: boolean;}
function canLogin(user: User): boolean { return user.isActive && user.isEmailVerified;}
// ユースケース2: 条件付き型との組み合わせtype NonNullable<T> = T extends null | undefined ? never : T;
function processValue<T>(value: T): NonNullable<T> { if (value === null || value === undefined) { throw new Error("Value cannot be null or undefined"); } return value;}null と undefined: 空値の表現
Section titled “null と undefined: 空値の表現”重要な区別:
// null: 意図的に空であることを示すlet user: User | null = null; // まだユーザーが設定されていない
// undefined: 値が設定されていないことを示すlet count: number | undefined; // 初期化されていない
// 実践的な使い分けinterface ApiResponse<T> { data: T | null; // APIが明示的にnullを返す場合 error?: string; // エラーはオプショナル(undefinedの可能性)}実践的なユースケース:
// ユースケース1: オプショナルチェーンとの組み合わせinterface User { name: string; address?: { street: string; city: string; };}
function getUserCity(user: User): string | undefined { return user.address?.city; // undefinedの可能性がある}
// ユースケース2: null合体演算子との組み合わせfunction getDisplayName(user: User | null): string { return user?.name ?? "Guest"; // nullまたはundefinedの場合にデフォルト値を使用}any: 型チェックの回避(非推奨)
Section titled “any: 型チェックの回避(非推奨)”なぜanyを避けるべきか:
// 問題のあるコード: anyの使用function processData(data: any) { return data.value + data.count; // 実行時エラーの可能性}
processData({ value: "hello", count: 5 }); // "hello5" という予期しない結果
// 解決: 適切な型定義interface Data { value: number; count: number;}
function processData(data: Data): number { return data.value + data.count; // 型安全}anyの代替手段:
// 1. unknownを使用(より安全)function processUnknown(data: unknown) { if (typeof data === 'object' && data !== null && 'value' in data) { // 型ガードで安全に処理 return (data as { value: number }).value; } throw new Error("Invalid data");}
// 2. ジェネリクスを使用function processGeneric<T extends { value: number }>(data: T): number { return data.value;}void: 戻り値がない関数
Section titled “void: 戻り値がない関数”基本的な使い方:
function logMessage(message: string): void { console.log(message); // 明示的なreturnがない}実践的なユースケース:
// ユースケース1: イベントハンドラーtype ClickHandler = (event: MouseEvent) => void;
const handleClick: ClickHandler = (event) => { console.log("Clicked", event.clientX, event.clientY); // void型なので、戻り値は使用されない};
// ユースケース2: 副作用のみの関数function updateLocalStorage(key: string, value: string): void { localStorage.setItem(key, value); // 戻り値は不要}2. 配列とオブジェクト 📦
Section titled “2. 配列とオブジェクト 📦”複数の値を扱うための型定義です。
要素の型を角括弧 [] で指定します。
string[]: 文字列の配列。例:['a', 'b', 'c']number[]: 数値の配列。例:[1, 2, 3]
オブジェクト
Section titled “オブジェクト”オブジェクトの各プロパティに型を定義します。
type User = { id: number; name: string; isLogin: boolean;};
const user: User = { id: 1, name: 'Alice', isLogin: true,};?(オプショナルプロパティ): プロパティ名の後ろに?を付けると、そのプロパティはあってもなくてもよくなります。
type OptionalUser = { id: number; name?: string; // 名前は任意};3. 関数 🤖
Section titled “3. 関数 🤖”関数の引数と戻り値に型を定義します。
- 引数: 引数名の後に
: 型を指定します。 - 戻り値: 引数リストの後に
: 型を指定します。
function add(x: number, y: number): number { return x + y;}
const result = add(1, 2); // result は number 型4. 複合型と高度な型 🧠
Section titled “4. 複合型と高度な型 🧠”複数の型を組み合わせたり、より柔軟な型を定義したりする方法です。
ユニオン型 (|)
Section titled “ユニオン型 (|)”複数の型のいずれかであることを示します。
let value: string | number;value = 'hello'; // OKvalue = 123; // OKインターセクション型 (&)
Section titled “インターセクション型 (&)”複数の型のすべてのプロパティを結合します。
type Admin = { role: string };type Employee = { id: number };
type AdminEmployee = Admin & Employee;
const admin: AdminEmployee = { role: 'admin', id: 1,};型エイリアス (type)
Section titled “型エイリアス (type)”既存の型に新しい名前を付けます。繰り返し使う複雑な型を簡潔に記述できます。
インターフェース (interface)
Section titled “インターフェース (interface)”主にオブジェクトの構造を定義するために使用します。型エイリアスと似ていますが、拡張(extends)や実装(implements)など、オブジェクト指向的な使い方ができます。
// 型エイリアスtype Point = { x: number; y: number };
// インターフェースinterface Shape { width: number; height: number;}5. ジェネリクス (Generics) 🤖
Section titled “5. ジェネリクス (Generics) 🤖”ジェネリクスは、再利用可能なコンポーネントを作成するための強力なツールです。特定の型に縛られることなく、複数の型に対応できる関数やクラスを定義できます。これにより、コードの柔軟性と型安全性の両方を高めることができます。
なぜジェネリクスが必要なのか
Section titled “なぜジェネリクスが必要なのか”問題のあるコード(型の重複):
// 問題: 同じロジックを複数の型で重複定義function getFirstString(items: string[]): string { return items[0];}
function getFirstNumber(items: number[]): number { return items[0];}
function getFirstUser(items: User[]): User { return items[0];}
// 解決: ジェネリクスで1つの関数に統一function getFirst<T>(items: T[]): T { return items[0];}
// すべての型で使用可能const firstString = getFirst<string>(["a", "b", "c"]);const firstNumber = getFirst<number>([1, 2, 3]);const firstUser = getFirst<User>([user1, user2]);実践的なユースケース
Section titled “実践的なユースケース”ユースケース1: APIレスポンスの型安全な処理
// ジェネリクスでAPIレスポンスを型安全にinterface ApiResponse<T> { data: T; status: number; message: string;}
async function fetchData<T>(url: string): Promise<ApiResponse<T>> { const response = await fetch(url); const json = await response.json(); return json as ApiResponse<T>;}
// 使用例: 型が推論されるinterface User { id: number; name: string;}
const userResponse = await fetchData<User>("/api/users/1");// userResponse.dataはUser型として推論されるconsole.log(userResponse.data.name); // 型安全ユースケース2: 状態管理ライブラリの実装
// ジェネリクスで型安全なストアを実装class Store<T> { private state: T; private listeners: Array<(state: T) => void> = [];
constructor(initialState: T) { this.state = initialState; }
getState(): T { return this.state; }
setState(newState: T): void { this.state = newState; this.listeners.forEach(listener => listener(this.state)); }
subscribe(listener: (state: T) => void): () => void { this.listeners.push(listener); return () => { this.listeners = this.listeners.filter(l => l !== listener); }; }}
// 使用例: 型が推論されるinterface AppState { user: User | null; theme: "light" | "dark";}
const store = new Store<AppState>({ user: null, theme: "light"});
// store.getState()はAppState型として推論されるconst state = store.getState();console.log(state.user?.name); // 型安全ユースケース3: 制約付きジェネリクス
// ジェネリクスに制約を追加interface HasId { id: number;}
function updateById<T extends HasId>(items: T[], id: number, updates: Partial<T>): T[] { return items.map(item => item.id === id ? { ...item, ...updates } : item );}
// 使用例: HasIdを実装している型のみ使用可能interface Product extends HasId { name: string; price: number;}
const products: Product[] = [ { id: 1, name: "Laptop", price: 1000 }, { id: 2, name: "Mouse", price: 20 }];
const updated = updateById(products, 1, { price: 900 });// updatedはProduct[]型として推論されるユースケース4: 複数の型パラメータ
// 複数の型パラメータを使用function mapArray<T, U>( array: T[], mapper: (item: T, index: number) => U): U[] { return array.map(mapper);}
// 使用例const numbers = [1, 2, 3];const strings = mapArray(numbers, (n) => n.toString());// stringsはstring[]型として推論される
const users: User[] = [ { id: 1, name: "Alice" }, { id: 2, name: "Bob" }];const names = mapArray(users, (user) => user.name);// namesはstring[]型として推論されるユースケース5: デフォルト型パラメータ
// デフォルト型パラメータを使用interface CacheOptions<T = any> { key: string; value: T; ttl?: number;}
// デフォルト型を使用const cache1: CacheOptions = { key: "user", value: "any型"};
// 明示的に型を指定const cache2: CacheOptions<User> = { key: "user", value: { id: 1, name: "Alice" }};6. ユーティリティ型 (Utility Types) 🛠️
Section titled “6. ユーティリティ型 (Utility Types) 🛠️”TypeScriptが標準で提供するユーティリティ型は、既存の型を変換して新しい型を生成するのに役立ちます。これらを適切に使用することで、型の重複を避け、保守性の高いコードを書けます。
Partial型: すべてのプロパティをオプショナルに
Section titled “Partial型: すべてのプロパティをオプショナルに”実践的なユースケース:
interface User { id: number; name: string; email: string; age: number;}
// ユースケース1: 更新用の型を作成type UserUpdate = Partial<User>;
function updateUser(id: number, updates: UserUpdate): void { // すべてのプロパティがオプショナルなので、部分的な更新が可能 // ...}
updateUser(1, { name: "Bob" }); // OK: 名前だけを更新updateUser(1, { age: 30 }); // OK: 年齢だけを更新updateUser(1, {}); // OK: 空のオブジェクトも可能
// ユースケース2: フォームの状態管理type UserFormState = Partial<User>;
const formState: UserFormState = { name: "Alice", // emailとageはまだ入力されていない};Required型: すべてのプロパティを必須に
Section titled “Required型: すべてのプロパティを必須に”実践的なユースケース:
interface Config { apiUrl?: string; timeout?: number; retries?: number;}
// ユースケース: デフォルト値を適用した後、すべて必須にするfunction createConfig(partial: Partial<Config>): Required<Config> { return { apiUrl: partial.apiUrl ?? "https://api.example.com", timeout: partial.timeout ?? 5000, retries: partial.retries ?? 3 };}
const config = createConfig({ apiUrl: "https://custom.api.com" });// configはすべてのプロパティが必須として扱われるconsole.log(config.timeout); // 型安全: undefinedの可能性がないReadonly型: すべてのプロパティを読み取り専用に
Section titled “Readonly型: すべてのプロパティを読み取り専用に”実践的なユースケース:
interface MutableState { count: number; items: string[];}
// ユースケース1: イミュータブルな状態を作成type ImmutableState = Readonly<MutableState>;
function processState(state: ImmutableState): void { // state.count = 10; // エラー: 読み取り専用プロパティに割り当てできない // state.items.push("new"); // エラー: 読み取り専用配列にpushできない}
// ユースケース2: 設定オブジェクトを保護interface AppConfig { apiUrl: string; features: string[];}
const config: Readonly<AppConfig> = { apiUrl: "https://api.example.com", features: ["feature1", "feature2"]};
// config.apiUrl = "new"; // エラー: 読み取り専用Pick型: 特定のプロパティを選択
Section titled “Pick型: 特定のプロパティを選択”実践的なユースケース:
interface User { id: number; name: string; email: string; password: string; age: number; createdAt: Date;}
// ユースケース1: 公開用の型を作成(パスワードを除外)type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
function getUserPublicInfo(user: User): PublicUser { return { id: user.id, name: user.name, email: user.email // passwordは含まれない };}
// ユースケース2: フォーム用の型を作成type UserFormData = Pick<User, 'name' | 'email' | 'age'>;
const formData: UserFormData = { name: "Alice", email: "alice@example.com", age: 30 // idやpasswordは不要};Omit型: 特定のプロパティを除外
Section titled “Omit型: 特定のプロパティを除外”実践的なユースケース:
// ユースケース: パスワードを除外した型を作成type UserWithoutPassword = Omit<User, 'password'>;
function sanitizeUser(user: User): UserWithoutPassword { const { password, ...rest } = user; return rest;}
// ユースケース2: 更新用の型(idとcreatedAtを除外)type UserUpdate = Omit<User, 'id' | 'createdAt'>;
function updateUser(id: number, updates: Partial<UserUpdate>): void { // idとcreatedAtは更新できない // ...}Record型: キーと値の型を指定したオブジェクト型
Section titled “Record型: キーと値の型を指定したオブジェクト型”実践的なユースケース:
// ユースケース1: 辞書型の作成type Status = "pending" | "success" | "error";type StatusMessages = Record<Status, string>;
const messages: StatusMessages = { pending: "処理中...", success: "成功しました", error: "エラーが発生しました"};
// ユースケース2: APIエンドポイントの型定義type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";type ApiEndpoints = Record<string, HttpMethod>;
const endpoints: ApiEndpoints = { "/api/users": "GET", "/api/users": "POST", "/api/users/:id": "PUT", "/api/users/:id": "DELETE"};Exclude型とExtract型
Section titled “Exclude型とExtract型”実践的なユースケース:
type AllStatus = "pending" | "success" | "error" | "cancelled";
// Exclude: 特定の型を除外type ActiveStatus = Exclude<AllStatus, "cancelled">;// ActiveStatus = "pending" | "success" | "error"
// Extract: 特定の型のみを抽出type FinalStatus = Extract<AllStatus, "success" | "error">;// FinalStatus = "success" | "error"
// ユースケース: 状態遷移の型定義function canTransition( from: ActiveStatus, to: FinalStatus): boolean { // 遷移ロジック return true;}7. 型推論 (Type Inference) と型アサーション (Type Assertion) 🧠
Section titled “7. 型推論 (Type Inference) と型アサーション (Type Assertion) 🧠”TypeScriptは、変数の初期値から自動的に型を推論します。
let message = "Hello"; // string 型と推論されるlet count = 100; // number 型と推論される明示的な型定義をしなくても、型安全性が保たれるため、コーディングの効率が向上します。
型アサーション
Section titled “型アサーション”開発者がコンパイラよりも型の情報を持っている場合に、asキーワードを使って型を上書きします。ただし、型安全性を損なう可能性があるため、慎重に使用すべきです。
const element = document.getElementById('my-element') as HTMLInputElement;// この時点でelementはHTMLInputElement型として扱われるelement.value = "newValue";8. enumとリテラル型 🔖
Section titled “8. enumとリテラル型 🔖”これらの型は、特定の値を厳密に制限したい場合に役立ちます。
enum (列挙型)
Section titled “enum (列挙型)”関連する定数をまとめて定義します。enumを使用すると、マジックナンバーや文字列リテラルを避けることができ、コードの可読性を向上させます。
enum Direction { Up, Down, Left, Right,}
const direction = Direction.Up; // directionは `0` になるenumのメンバーには、デフォルトで数値が割り当てられますが、文字列を割り当てることもできます。
厳密に特定の文字列、数値、または真偽値のみを受け入れるようにします。
type Status = "pending" | "success" | "error";
let currentStatus: Status = "pending";currentStatus = "success"; // OKcurrentStatus = "failure"; // エラー: '"failure"' 型は 'Status' 型に割り当てられません。これは、ユニオン型の一種で、特定の文字列の集合だけを許可したい場合に非常に便利です。
9. 構造的部分型 (Structural Subtyping) 🦢
Section titled “9. 構造的部分型 (Structural Subtyping) 🦢”TypeScriptの型システムは、構造的部分型に基づいています。これは、オブジェクトが特定の型として認識されるために、その構造が一致していればよいという概念です。
例えば、Pointというインターフェースがあるとします。
interface Point { x: number; y: number;}そして、Vectorというオブジェクトがあります。
const vector = { x: 10, y: 20, z: 30 };Vectorにはzプロパティがありますが、Pointインターフェースが持つxとyのプロパティも持っているため、Vectorは**Point型として扱えます**。
function printPoint(p: Point) { console.log(p.x, p.y);}
printPoint(vector); // エラーにならないこの柔軟な型チェックは、TypeScriptの大きな特徴であり、JavaScriptとの互換性を保つ上で重要な役割を果たします。
10. unknownと型ガード 🛡️
Section titled “10. unknownと型ガード 🛡️”any型は型チェックを完全に無効にしますが、unknown型はより安全な代替手段を提供します。
unknown
Section titled “unknown”unknown型の変数は、型を特定しない限り、プロパティへのアクセスや操作ができません。これにより、型安全性を強制します。
let value: unknown;value = 'hello';
// エラー: 'value' は unknown です。// value.toUpperCase();
if (typeof value === 'string') { // ここでは value は string 型として認識される console.log(value.toUpperCase()); // OK}typeofやinstanceofといった条件文を使って、実行時に変数の型を絞り込むことを「型ガード」と呼びます。これは、unknown型を安全に扱うために不可欠です。
11. 条件型 (Conditional Types) 🤔
Section titled “11. 条件型 (Conditional Types) 🤔”条件型は、ある型が別の型に割り当て可能であるかどうかに基づいて、異なる型を返すことができる高度な機能です。三項演算子に似た構文を使用します。これは、特にジェネリクスと組み合わせて使用され、非常に柔軟な型を定義する際に役立ちます。
なぜ条件型が必要なのか
Section titled “なぜ条件型が必要なのか”問題: 型に応じた処理の分岐
// 問題: 型に応じて異なる処理が必要function processValue(value: string | number) { if (typeof value === 'string') { return value.toUpperCase(); } else { return value.toFixed(2); }}
// 問題: 戻り値の型が正確に推論されないconst result = processValue("hello"); // string | string型(不正確)解決: 条件型で型を正確に推論
// 解決: 条件型で戻り値の型を正確に定義type ProcessResult<T> = T extends string ? string : number;
function processValue<T extends string | number>( value: T): ProcessResult<T> { if (typeof value === 'string') { return value.toUpperCase() as ProcessResult<T>; } else { return value.toFixed(2) as ProcessResult<T>; }}
const strResult = processValue("hello"); // string型として推論const numResult = processValue(123); // number型として推論実践的なユースケース
Section titled “実践的なユースケース”ユースケース1: APIレスポンスの型を条件付きで定義
// エンドポイントに応じてレスポンスの型を変更type ApiEndpoint = | { path: "/users"; method: "GET" } | { path: "/users/:id"; method: "GET" } | { path: "/users"; method: "POST" };
type ApiResponse<T extends ApiEndpoint> = T extends { path: "/users"; method: "GET" } ? User[] : T extends { path: "/users/:id"; method: "GET" } ? User : T extends { path: "/users"; method: "POST" } ? { id: number } : never;
async function apiCall<T extends ApiEndpoint>( endpoint: T): Promise<ApiResponse<T>> { // 実装 return {} as ApiResponse<T>;}
// 型が正確に推論されるconst users = await apiCall({ path: "/users", method: "GET" });// usersはUser[]型
const user = await apiCall({ path: "/users/:id", method: "GET" });// userはUser型ユースケース2: 配列と非配列の区別
// 配列かどうかで型を分岐type Flatten<T> = T extends (infer U)[] ? U : T;
type StringArray = Flatten<string[]>; // stringtype StringValue = Flatten<string>; // string
// 実践的な使用例function getFirst<T>(value: T | T[]): T { return Array.isArray(value) ? value[0] : value;}
// 型が正確に推論されるconst first = getFirst([1, 2, 3]); // number型const single = getFirst(42); // number型ユースケース3: 関数の戻り値の型を抽出
// 関数型から戻り値の型を抽出type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser(): User { return { id: 1, name: "Alice" };}
type UserReturn = ReturnType<typeof getUser>; // User型
// 実践的な使用例: 非同期関数の戻り値type AsyncReturnType<T> = T extends (...args: any[]) => Promise<infer R> ? R : T extends (...args: any[]) => infer R ? R : never;
async function fetchUser(): Promise<User> { return { id: 1, name: "Alice" };}
type FetchedUser = AsyncReturnType<typeof fetchUser>; // User型ユースケース4: オプショナルプロパティの判定
// プロパティがオプショナルかどうかを判定type IsOptional<T, K extends keyof T> = {} extends Pick<T, K> ? true : false;
interface User { id: number; name: string; email?: string;}
type IsEmailOptional = IsOptional<User, "email">; // truetype IsNameOptional = IsOptional<User, "name">; // falseユースケース5: ネストした条件型
// 複雑な条件型の組み合わせtype NonNullable<T> = T extends null | undefined ? never : T;
type DeepNonNullable<T> = T extends object ? { [P in keyof T]: T[P] extends (infer U)[] ? DeepNonNullable<U>[] : T[P] extends object ? DeepNonNullable<T[P]> : NonNullable<T[P]>; } : NonNullable<T>;
interface Config { apiUrl: string | null; features: Array<{ name: string | undefined; enabled: boolean | null; }>;}
type StrictConfig = DeepNonNullable<Config>;// すべてのnullとundefinedが除去された型この機能は、複雑なライブラリやフレームワークの型定義でよく見られます。
12. マッピング型 (Mapped Types) 🗺️
Section titled “12. マッピング型 (Mapped Types) 🗺️”マッピング型は、既存の型に基づいて新しい型を作成する際に使用されます。オブジェクトの各プロパティをループ処理し、新しい型に変換することができます。これにより、型の変換を自動化し、型の重複を避けることができます。
なぜマッピング型が必要なのか
Section titled “なぜマッピング型が必要なのか”問題: 型の変換の重複
// 問題: 同じような型定義を繰り返し書く必要があるinterface User { id: number; name: string; email: string;}
interface ReadonlyUser { readonly id: number; readonly name: string; readonly email: string;}
interface OptionalUser { id?: number; name?: string; email?: string;}
// プロパティが増えるたびに、すべての型を更新する必要がある解決: マッピング型で自動変換
// 解決: マッピング型で自動的に変換type ReadonlyUser = { readonly [P in keyof User]: User[P];};
type OptionalUser = { [P in keyof User]?: User[P];};
// Userが変更されても、自動的に反映される実践的なユースケース
Section titled “実践的なユースケース”ユースケース1: すべてのプロパティを読み取り専用にする
type User = { name: string; age: number;};
// Userのすべてのプロパティを読み取り専用にするtype ReadonlyUser = { readonly [P in keyof User]: User[P];};
const user: ReadonlyUser = { name: 'Alice', age: 30,};
// エラー: 'name' は読み取り専用プロパティであるため、割り当てできません。// user.name = 'Bob';ユースケース2: すべてのプロパティをオプショナルにする
// Partial<T>の実装type MyPartial<T> = { [P in keyof T]?: T[P];};
type OptionalUser = MyPartial<User>;
const partialUser: OptionalUser = { name: "Alice" // ageは省略可能};ユースケース3: 特定のプロパティのみを変換
// 文字列プロパティのみをオプショナルにするtype OptionalStringProps<T> = { [P in keyof T]: T[P] extends string ? T[P] | undefined : T[P];};
interface Config { apiUrl: string; timeout: number; retries: number;}
type FlexibleConfig = OptionalStringProps<Config>;// apiUrlはstring | undefined、timeoutとretriesはnumberのままユースケース4: プロパティ名の変換
// プロパティ名にプレフィックスを追加type Prefixed<T, Prefix extends string> = { [P in keyof T as `${Prefix}${Capitalize<string & P>}`]: T[P];};
interface User { name: string; age: number;}
type PrefixedUser = Prefixed<User, "user">;// { userName: string; userAge: number; }ユースケース5: 条件付きマッピング型
// 関数型のプロパティのみを抽出type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never;}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
interface UserService { id: number; name: string; getName(): string; updateName(name: string): void;}
type UserServiceMethods = FunctionProperties<UserService>;// { getName: () => string; updateName: (name: string) => void; }ユースケース6: ネストしたオブジェクトの変換
// 深い階層のオブジェクトも変換type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? T[P] extends Function ? T[P] : DeepReadonly<T[P]> : T[P];};
interface Config { api: { url: string; timeout: number; }; features: string[];}
type ReadonlyConfig = DeepReadonly<Config>;// すべてのプロパティが読み取り専用この例では、Readonly<T>ユーティリティ型を自作していますが、このように既存の型を変換する処理を一般化することができます。
13. デコレーター (Decorators) 🎨
Section titled “13. デコレーター (Decorators) 🎨”デコレーターは、クラスやプロパティ、メソッドなどに特別な動作をアタッチする機能です。実験的な機能ですが、AngularやNestJSといったフレームワークで広く使われています。
デコレーターは@シンボルを使って適用されます。
function log(target, key, descriptor) { const originalMethod = descriptor.value; descriptor.value = function(...args: any[]) { console.log(`Calling ${key} with`, args); return originalMethod.apply(this, args); }; return descriptor;}
class Calculator { @log add(x: number, y: number) { return x + y; }}このコードでは、addメソッドが呼び出されるたびに、自動的に引数がログ出力されます。デコレーターは、ロギング、認証、トランザクション管理といった横断的な関心を、コードを汚すことなく実装するのに役立ちます。
これらのトピックを追加することで、ドキュメントはTypeScriptの型システムに関する非常に高度で包括的なリソースになります。 role: ‘admin’, id: 1, };
### 型エイリアス (`type`)
既存の型に新しい名前を付けます。繰り返し使う複雑な型を簡潔に記述できます。
### インターフェース (`interface`)
主にオブジェクトの構造を定義するために使用します。型エイリアスと似ていますが、拡張(`extends`)や実装(`implements`)など、オブジェクト指向的な使い方ができます。
```typescript// 型エイリアスtype Point = { x: number; y: number };
// インターフェースinterface Shape { width: number; height: number;}5. ジェネリクス (Generics) 🤖
Section titled “5. ジェネリクス (Generics) 🤖”ジェネリクスは、再利用可能なコンポーネントを作成するための強力なツールです。特定の型に縛られることなく、複数の型に対応できる関数やクラスを定義できます。これにより、コードの柔軟性と型安全性の両方を高めることができます。
なぜジェネリクスが必要なのか
Section titled “なぜジェネリクスが必要なのか”問題のあるコード(型の重複):
// 問題: 同じロジックを複数の型で重複定義function getFirstString(items: string[]): string { return items[0];}
function getFirstNumber(items: number[]): number { return items[0];}
function getFirstUser(items: User[]): User { return items[0];}
// 解決: ジェネリクスで1つの関数に統一function getFirst<T>(items: T[]): T { return items[0];}
// すべての型で使用可能const firstString = getFirst<string>(["a", "b", "c"]);const firstNumber = getFirst<number>([1, 2, 3]);const firstUser = getFirst<User>([user1, user2]);実践的なユースケース
Section titled “実践的なユースケース”ユースケース1: APIレスポンスの型安全な処理
// ジェネリクスでAPIレスポンスを型安全にinterface ApiResponse<T> { data: T; status: number; message: string;}
async function fetchData<T>(url: string): Promise<ApiResponse<T>> { const response = await fetch(url); const json = await response.json(); return json as ApiResponse<T>;}
// 使用例: 型が推論されるinterface User { id: number; name: string;}
const userResponse = await fetchData<User>("/api/users/1");// userResponse.dataはUser型として推論されるconsole.log(userResponse.data.name); // 型安全ユースケース2: 状態管理ライブラリの実装
// ジェネリクスで型安全なストアを実装class Store<T> { private state: T; private listeners: Array<(state: T) => void> = [];
constructor(initialState: T) { this.state = initialState; }
getState(): T { return this.state; }
setState(newState: T): void { this.state = newState; this.listeners.forEach(listener => listener(this.state)); }
subscribe(listener: (state: T) => void): () => void { this.listeners.push(listener); return () => { this.listeners = this.listeners.filter(l => l !== listener); }; }}
// 使用例: 型が推論されるinterface AppState { user: User | null; theme: "light" | "dark";}
const store = new Store<AppState>({ user: null, theme: "light"});
// store.getState()はAppState型として推論されるconst state = store.getState();console.log(state.user?.name); // 型安全ユースケース3: 制約付きジェネリクス
// ジェネリクスに制約を追加interface HasId { id: number;}
function updateById<T extends HasId>(items: T[], id: number, updates: Partial<T>): T[] { return items.map(item => item.id === id ? { ...item, ...updates } : item );}
// 使用例: HasIdを実装している型のみ使用可能interface Product extends HasId { name: string; price: number;}
const products: Product[] = [ { id: 1, name: "Laptop", price: 1000 }, { id: 2, name: "Mouse", price: 20 }];
const updated = updateById(products, 1, { price: 900 });// updatedはProduct[]型として推論されるユースケース4: 複数の型パラメータ
// 複数の型パラメータを使用function mapArray<T, U>( array: T[], mapper: (item: T, index: number) => U): U[] { return array.map(mapper);}
// 使用例const numbers = [1, 2, 3];const strings = mapArray(numbers, (n) => n.toString());// stringsはstring[]型として推論される
const users: User[] = [ { id: 1, name: "Alice" }, { id: 2, name: "Bob" }];const names = mapArray(users, (user) => user.name);// namesはstring[]型として推論されるユースケース5: デフォルト型パラメータ
// デフォルト型パラメータを使用interface CacheOptions<T = any> { key: string; value: T; ttl?: number;}
// デフォルト型を使用const cache1: CacheOptions = { key: "user", value: "any型"};
// 明示的に型を指定const cache2: CacheOptions<User> = { key: "user", value: { id: 1, name: "Alice" }};6. ユーティリティ型 (Utility Types) 🛠️
Section titled “6. ユーティリティ型 (Utility Types) 🛠️”TypeScriptが標準で提供するユーティリティ型は、既存の型を変換して新しい型を生成するのに役立ちます。これらを適切に使用することで、型の重複を避け、保守性の高いコードを書けます。
Partial型: すべてのプロパティをオプショナルに
Section titled “Partial型: すべてのプロパティをオプショナルに”実践的なユースケース:
interface User { id: number; name: string; email: string; age: number;}
// ユースケース1: 更新用の型を作成type UserUpdate = Partial<User>;
function updateUser(id: number, updates: UserUpdate): void { // すべてのプロパティがオプショナルなので、部分的な更新が可能 // ...}
updateUser(1, { name: "Bob" }); // OK: 名前だけを更新updateUser(1, { age: 30 }); // OK: 年齢だけを更新updateUser(1, {}); // OK: 空のオブジェクトも可能
// ユースケース2: フォームの状態管理type UserFormState = Partial<User>;
const formState: UserFormState = { name: "Alice", // emailとageはまだ入力されていない};Required型: すべてのプロパティを必須に
Section titled “Required型: すべてのプロパティを必須に”実践的なユースケース:
interface Config { apiUrl?: string; timeout?: number; retries?: number;}
// ユースケース: デフォルト値を適用した後、すべて必須にするfunction createConfig(partial: Partial<Config>): Required<Config> { return { apiUrl: partial.apiUrl ?? "https://api.example.com", timeout: partial.timeout ?? 5000, retries: partial.retries ?? 3 };}
const config = createConfig({ apiUrl: "https://custom.api.com" });// configはすべてのプロパティが必須として扱われるconsole.log(config.timeout); // 型安全: undefinedの可能性がないReadonly型: すべてのプロパティを読み取り専用に
Section titled “Readonly型: すべてのプロパティを読み取り専用に”実践的なユースケース:
interface MutableState { count: number; items: string[];}
// ユースケース1: イミュータブルな状態を作成type ImmutableState = Readonly<MutableState>;
function processState(state: ImmutableState): void { // state.count = 10; // エラー: 読み取り専用プロパティに割り当てできない // state.items.push("new"); // エラー: 読み取り専用配列にpushできない}
// ユースケース2: 設定オブジェクトを保護interface AppConfig { apiUrl: string; features: string[];}
const config: Readonly<AppConfig> = { apiUrl: "https://api.example.com", features: ["feature1", "feature2"]};
// config.apiUrl = "new"; // エラー: 読み取り専用Pick型: 特定のプロパティを選択
Section titled “Pick型: 特定のプロパティを選択”実践的なユースケース:
interface User { id: number; name: string; email: string; password: string; age: number; createdAt: Date;}
// ユースケース1: 公開用の型を作成(パスワードを除外)type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
function getUserPublicInfo(user: User): PublicUser { return { id: user.id, name: user.name, email: user.email // passwordは含まれない };}
// ユースケース2: フォーム用の型を作成type UserFormData = Pick<User, 'name' | 'email' | 'age'>;
const formData: UserFormData = { name: "Alice", email: "alice@example.com", age: 30 // idやpasswordは不要};Omit型: 特定のプロパティを除外
Section titled “Omit型: 特定のプロパティを除外”実践的なユースケース:
// ユースケース: パスワードを除外した型を作成type UserWithoutPassword = Omit<User, 'password'>;
function sanitizeUser(user: User): UserWithoutPassword { const { password, ...rest } = user; return rest;}
// ユースケース2: 更新用の型(idとcreatedAtを除外)type UserUpdate = Omit<User, 'id' | 'createdAt'>;
function updateUser(id: number, updates: Partial<UserUpdate>): void { // idとcreatedAtは更新できない // ...}Record型: キーと値の型を指定したオブジェクト型
Section titled “Record型: キーと値の型を指定したオブジェクト型”実践的なユースケース:
// ユースケース1: 辞書型の作成type Status = "pending" | "success" | "error";type StatusMessages = Record<Status, string>;
const messages: StatusMessages = { pending: "処理中...", success: "成功しました", error: "エラーが発生しました"};
// ユースケース2: APIエンドポイントの型定義type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";type ApiEndpoints = Record<string, HttpMethod>;
const endpoints: ApiEndpoints = { "/api/users": "GET", "/api/users": "POST", "/api/users/:id": "PUT", "/api/users/:id": "DELETE"};Exclude型とExtract型
Section titled “Exclude型とExtract型”実践的なユースケース:
type AllStatus = "pending" | "success" | "error" | "cancelled";
// Exclude: 特定の型を除外type ActiveStatus = Exclude<AllStatus, "cancelled">;// ActiveStatus = "pending" | "success" | "error"
// Extract: 特定の型のみを抽出type FinalStatus = Extract<AllStatus, "success" | "error">;// FinalStatus = "success" | "error"
// ユースケース: 状態遷移の型定義function canTransition( from: ActiveStatus, to: FinalStatus): boolean { // 遷移ロジック return true;}7. 型推論 (Type Inference) と型アサーション (Type Assertion) 🧠
Section titled “7. 型推論 (Type Inference) と型アサーション (Type Assertion) 🧠”TypeScriptは、変数の初期値から自動的に型を推論します。
let message = "Hello"; // string 型と推論されるlet count = 100; // number 型と推論される明示的な型定義をしなくても、型安全性が保たれるため、コーディングの効率が向上します。
型アサーション
Section titled “型アサーション”開発者がコンパイラよりも型の情報を持っている場合に、asキーワードを使って型を上書きします。ただし、型安全性を損なう可能性があるため、慎重に使用すべきです。
const element = document.getElementById('my-element') as HTMLInputElement;// この時点でelementはHTMLInputElement型として扱われるelement.value = "newValue";8. enumとリテラル型 🔖
Section titled “8. enumとリテラル型 🔖”これらの型は、特定の値を厳密に制限したい場合に役立ちます。
enum (列挙型)
Section titled “enum (列挙型)”関連する定数をまとめて定義します。enumを使用すると、マジックナンバーや文字列リテラルを避けることができ、コードの可読性を向上させます。
enum Direction { Up, Down, Left, Right,}
const direction = Direction.Up; // directionは `0` になるenumのメンバーには、デフォルトで数値が割り当てられますが、文字列を割り当てることもできます。
厳密に特定の文字列、数値、または真偽値のみを受け入れるようにします。
type Status = "pending" | "success" | "error";
let currentStatus: Status = "pending";currentStatus = "success"; // OKcurrentStatus = "failure"; // エラー: '"failure"' 型は 'Status' 型に割り当てられません。これは、ユニオン型の一種で、特定の文字列の集合だけを許可したい場合に非常に便利です。
9. 構造的部分型 (Structural Subtyping) 🦢
Section titled “9. 構造的部分型 (Structural Subtyping) 🦢”TypeScriptの型システムは、構造的部分型に基づいています。これは、オブジェクトが特定の型として認識されるために、その構造が一致していればよいという概念です。
例えば、Pointというインターフェースがあるとします。
interface Point { x: number; y: number;}そして、Vectorというオブジェクトがあります。
const vector = { x: 10, y: 20, z: 30 };Vectorにはzプロパティがありますが、Pointインターフェースが持つxとyのプロパティも持っているため、Vectorは**Point型として扱えます**。
function printPoint(p: Point) { console.log(p.x, p.y);}
printPoint(vector); // エラーにならないこの柔軟な型チェックは、TypeScriptの大きな特徴であり、JavaScriptとの互換性を保つ上で重要な役割を果たします。
10. unknownと型ガード 🛡️
Section titled “10. unknownと型ガード 🛡️”any型は型チェックを完全に無効にしますが、unknown型はより安全な代替手段を提供します。
unknown
Section titled “unknown”unknown型の変数は、型を特定しない限り、プロパティへのアクセスや操作ができません。これにより、型安全性を強制します。
let value: unknown;value = 'hello';
// エラー: 'value' は unknown です。// value.toUpperCase();
if (typeof value === 'string') { // ここでは value は string 型として認識される console.log(value.toUpperCase()); // OK}typeofやinstanceofといった条件文を使って、実行時に変数の型を絞り込むことを「型ガード」と呼びます。これは、unknown型を安全に扱うために不可欠です。
11. 条件型 (Conditional Types) 🤔
Section titled “11. 条件型 (Conditional Types) 🤔”条件型は、ある型が別の型に割り当て可能であるかどうかに基づいて、異なる型を返すことができる高度な機能です。三項演算子に似た構文を使用します。これは、特にジェネリクスと組み合わせて使用され、非常に柔軟な型を定義する際に役立ちます。
なぜ条件型が必要なのか
Section titled “なぜ条件型が必要なのか”問題: 型に応じた処理の分岐
// 問題: 型に応じて異なる処理が必要function processValue(value: string | number) { if (typeof value === 'string') { return value.toUpperCase(); } else { return value.toFixed(2); }}
// 問題: 戻り値の型が正確に推論されないconst result = processValue("hello"); // string | string型(不正確)解決: 条件型で型を正確に推論
// 解決: 条件型で戻り値の型を正確に定義type ProcessResult<T> = T extends string ? string : number;
function processValue<T extends string | number>( value: T): ProcessResult<T> { if (typeof value === 'string') { return value.toUpperCase() as ProcessResult<T>; } else { return value.toFixed(2) as ProcessResult<T>; }}
const strResult = processValue("hello"); // string型として推論const numResult = processValue(123); // number型として推論実践的なユースケース
Section titled “実践的なユースケース”ユースケース1: APIレスポンスの型を条件付きで定義
// エンドポイントに応じてレスポンスの型を変更type ApiEndpoint = | { path: "/users"; method: "GET" } | { path: "/users/:id"; method: "GET" } | { path: "/users"; method: "POST" };
type ApiResponse<T extends ApiEndpoint> = T extends { path: "/users"; method: "GET" } ? User[] : T extends { path: "/users/:id"; method: "GET" } ? User : T extends { path: "/users"; method: "POST" } ? { id: number } : never;
async function apiCall<T extends ApiEndpoint>( endpoint: T): Promise<ApiResponse<T>> { // 実装 return {} as ApiResponse<T>;}
// 型が正確に推論されるconst users = await apiCall({ path: "/users", method: "GET" });// usersはUser[]型
const user = await apiCall({ path: "/users/:id", method: "GET" });// userはUser型ユースケース2: 配列と非配列の区別
// 配列かどうかで型を分岐type Flatten<T> = T extends (infer U)[] ? U : T;
type StringArray = Flatten<string[]>; // stringtype StringValue = Flatten<string>; // string
// 実践的な使用例function getFirst<T>(value: T | T[]): T { return Array.isArray(value) ? value[0] : value;}
// 型が正確に推論されるconst first = getFirst([1, 2, 3]); // number型const single = getFirst(42); // number型ユースケース3: 関数の戻り値の型を抽出
// 関数型から戻り値の型を抽出type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser(): User { return { id: 1, name: "Alice" };}
type UserReturn = ReturnType<typeof getUser>; // User型
// 実践的な使用例: 非同期関数の戻り値type AsyncReturnType<T> = T extends (...args: any[]) => Promise<infer R> ? R : T extends (...args: any[]) => infer R ? R : never;
async function fetchUser(): Promise<User> { return { id: 1, name: "Alice" };}
type FetchedUser = AsyncReturnType<typeof fetchUser>; // User型ユースケース4: オプショナルプロパティの判定
// プロパティがオプショナルかどうかを判定type IsOptional<T, K extends keyof T> = {} extends Pick<T, K> ? true : false;
interface User { id: number; name: string; email?: string;}
type IsEmailOptional = IsOptional<User, "email">; // truetype IsNameOptional = IsOptional<User, "name">; // falseユースケース5: ネストした条件型
// 複雑な条件型の組み合わせtype NonNullable<T> = T extends null | undefined ? never : T;
type DeepNonNullable<T> = T extends object ? { [P in keyof T]: T[P] extends (infer U)[] ? DeepNonNullable<U>[] : T[P] extends object ? DeepNonNullable<T[P]> : NonNullable<T[P]>; } : NonNullable<T>;
interface Config { apiUrl: string | null; features: Array<{ name: string | undefined; enabled: boolean | null; }>;}
type StrictConfig = DeepNonNullable<Config>;// すべてのnullとundefinedが除去された型この機能は、複雑なライブラリやフレームワークの型定義でよく見られます。
12. マッピング型 (Mapped Types) 🗺️
Section titled “12. マッピング型 (Mapped Types) 🗺️”マッピング型は、既存の型に基づいて新しい型を作成する際に使用されます。オブジェクトの各プロパティをループ処理し、新しい型に変換することができます。これにより、型の変換を自動化し、型の重複を避けることができます。
なぜマッピング型が必要なのか
Section titled “なぜマッピング型が必要なのか”問題: 型の変換の重複
// 問題: 同じような型定義を繰り返し書く必要があるinterface User { id: number; name: string; email: string;}
interface ReadonlyUser { readonly id: number; readonly name: string; readonly email: string;}
interface OptionalUser { id?: number; name?: string; email?: string;}
// プロパティが増えるたびに、すべての型を更新する必要がある解決: マッピング型で自動変換
// 解決: マッピング型で自動的に変換type ReadonlyUser = { readonly [P in keyof User]: User[P];};
type OptionalUser = { [P in keyof User]?: User[P];};
// Userが変更されても、自動的に反映される実践的なユースケース
Section titled “実践的なユースケース”ユースケース1: すべてのプロパティを読み取り専用にする
type User = { name: string; age: number;};
// Userのすべてのプロパティを読み取り専用にするtype ReadonlyUser = { readonly [P in keyof User]: User[P];};
const user: ReadonlyUser = { name: 'Alice', age: 30,};
// エラー: 'name' は読み取り専用プロパティであるため、割り当てできません。// user.name = 'Bob';ユースケース2: すべてのプロパティをオプショナルにする
// Partial<T>の実装type MyPartial<T> = { [P in keyof T]?: T[P];};
type OptionalUser = MyPartial<User>;
const partialUser: OptionalUser = { name: "Alice" // ageは省略可能};ユースケース3: 特定のプロパティのみを変換
// 文字列プロパティのみをオプショナルにするtype OptionalStringProps<T> = { [P in keyof T]: T[P] extends string ? T[P] | undefined : T[P];};
interface Config { apiUrl: string; timeout: number; retries: number;}
type FlexibleConfig = OptionalStringProps<Config>;// apiUrlはstring | undefined、timeoutとretriesはnumberのままユースケース4: プロパティ名の変換
// プロパティ名にプレフィックスを追加type Prefixed<T, Prefix extends string> = { [P in keyof T as `${Prefix}${Capitalize<string & P>}`]: T[P];};
interface User { name: string; age: number;}
type PrefixedUser = Prefixed<User, "user">;// { userName: string; userAge: number; }ユースケース5: 条件付きマッピング型
// 関数型のプロパティのみを抽出type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never;}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
interface UserService { id: number; name: string; getName(): string; updateName(name: string): void;}
type UserServiceMethods = FunctionProperties<UserService>;// { getName: () => string; updateName: (name: string) => void; }ユースケース6: ネストしたオブジェクトの変換
// 深い階層のオブジェクトも変換type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? T[P] extends Function ? T[P] : DeepReadonly<T[P]> : T[P];};
interface Config { api: { url: string; timeout: number; }; features: string[];}
type ReadonlyConfig = DeepReadonly<Config>;// すべてのプロパティが読み取り専用この例では、Readonly<T>ユーティリティ型を自作していますが、このように既存の型を変換する処理を一般化することができます。
13. デコレーター (Decorators) 🎨
Section titled “13. デコレーター (Decorators) 🎨”デコレーターは、クラスやプロパティ、メソッドなどに特別な動作をアタッチする機能です。実験的な機能ですが、AngularやNestJSといったフレームワークで広く使われています。
デコレーターは@シンボルを使って適用されます。
function log(target, key, descriptor) { const originalMethod = descriptor.value; descriptor.value = function(...args: any[]) { console.log(`Calling ${key} with`, args); return originalMethod.apply(this, args); }; return descriptor;}
class Calculator { @log add(x: number, y: number) { return x + y; }}このコードでは、addメソッドが呼び出されるたびに、自動的に引数がログ出力されます。デコレーターは、ロギング、認証、トランザクション管理といった横断的な関心を、コードを汚すことなく実装するのに役立ちます。
これらのトピックを追加することで、ドキュメントはTypeScriptの型システムに関する非常に高度で包括的なリソースになります。