【TypeScript】カスタムタイプガードの実装パターン - 型安全な条件分岐とエラーチェック

【TypeScript】カスタムタイプガードの実装パターン - 型安全な条件分岐とエラーチェック

2024-11-10

2024-11-10

TypeScriptのカスタムタイプガードとは

TypeScriptカスタムタイプガードは、特定の条件に基づいて値が特定の型であることを判定するための関数です。これにより、コードの安全性と可読性が向上し、型安全なデータ処理が可能になります。特に、APIから受け取ったデータが期待する型かどうかを判定する際に活用できます。 カスタムタイプガードは、TypeScriptの型推論をさらに強力にするため、さまざまな場面で有効です。ここでは、カスタムタイプガードの基本から実践的な使い方までを解説します。

カスタムタイプガードの基本構文

カスタムタイプガードの関数は、返り値にvalue is 型という構文を使用します。この返り値の形式によって、TypeScriptは条件が満たされた場合に特定の型を推論できます。

function isString(value: any): value is string {
    return typeof value === "string";
}

上記の例では、isString関数がvalueが文字列型かどうかを判定しています。この関数がtrueを返した場合、TypeScriptvaluestring型であると認識します。

カスタムタイプガードの実践例

例1: ユニオン型での条件分岐

例えば、ユニオン型string | numberの変数に対して、数値である場合のみ特定の処理を行いたいとします。この場合、カスタムタイプガードを使って型を判別できます。

function isNumber(value: any): value is number {
    return typeof value === "number";
}
function processValue(value: string | number) {
    if (isNumber(value)) {
        // valueはnumber型とみなされる
        console.log(`Number: ${value.toFixed(2)}`);
    } else {
        // valueはstring型とみなされる
        console.log(`String: ${value.toUpperCase()}`);
    }
}
processValue(42);        // 出力: Number: 42.00
processValue("hello");   // 出力: String: HELLO

isNumber関数がtrueを返すと、valuenumber型として認識され、number型固有のメソッドtoFixedが使用可能になります。

例2: オブジェクトの特定プロパティの存在を確認

特定のプロパティを持つかどうかで型を判断したい場合にも、カスタムタイプガードを使います。以下の例では、AdminUser型とGuestUser型を区別するために、isAdminUser関数を使用しています。

interface AdminUser {
    role: "admin";
    privileges: string[];
}
interface GuestUser {
    role: "guest";
}
type User = AdminUser | GuestUser;
function isAdminUser(user: User): user is AdminUser {
    return "privileges" in user;
}
function showPrivileges(user: User) {
    if (isAdminUser(user)) {
        console.log(`Admin privileges: ${user.privileges.join(", ")}`);
    } else {
        console.log("No privileges for guest user.");
    }
}
const admin: User = { role: "admin", privileges: ["manage-users", "edit-settings"] };
const guest: User = { role: "guest" };
showPrivileges(admin); // 出力: Admin privileges: manage-users, edit-settings
showPrivileges(guest); // 出力: No privileges for guest user.

この例では、isAdminUser関数によってprivilegesプロパティの有無を判定し、AdminUser型かどうかを確認しています。

複雑なカスタムタイプガードの実装パターン

例3: APIレスポンスの検証

外部APIのレスポンスは必ずしも型が保証されていないため、カスタムタイプガードを使用して型安全に処理できます。例えば、APIレスポンスがSuccessResponse型かErrorResponse型かを判定する関数を作成します。

interface SuccessResponse {
    status: "success";
    data: any;
}
interface ErrorResponse {
    status: "error";
    message: string;
}
type ApiResponse = SuccessResponse | ErrorResponse;
function isSuccessResponse(response: ApiResponse): response is SuccessResponse {
    return response.status === "success";
}
function handleResponse(response: ApiResponse) {
    if (isSuccessResponse(response)) {
        console.log("Data:", response.data);
    } else {
        console.error("Error:", response.message);
    }
}
// 使用例
const response1: ApiResponse = { status: "success", data: { id: 1, name: "Alice" } };
const response2: ApiResponse = { status: "error", message: "Not found" };
handleResponse(response1); // 出力: Data: { id: 1, name: "Alice" }
handleResponse(response2); // 出力: Error: Not found

ここでは、statusプロパティの値でレスポンスを判定し、SuccessResponseErrorResponseかに応じて異なる処理を行っています。

例4: ネストされたオブジェクトの型チェック

カスタムタイプガードを使えば、ネストされたオブジェクトのプロパティに対する型チェックも行えます。以下の例では、ネストされたAddressプロパティを持つかどうかで型を判定しています。

interface UserWithAddress {
    name: string;
    address: {
        city: string;
        zipCode: string;
    };
}
interface UserWithoutAddress {
    name: string;
}
type User = UserWithAddress | UserWithoutAddress;
function hasAddress(user: User): user is UserWithAddress {
    return "address" in user && typeof user.address === "object";
}
function showUserAddress(user: User) {
    if (hasAddress(user)) {
        console.log(`City: ${user.address.city}, Zip: ${user.address.zipCode}`);
    } else {
        console.log("No address available.");
    }
}
const user1: User = { name: "Alice", address: { city: "New York", zipCode: "10001" } };
const user2: User = { name: "Bob" };
showUserAddress(user1); // 出力: City: New York, Zip: 10001
showUserAddress(user2); // 出力: No address available.

この例では、addressプロパティが存在し、かつその型がobjectであることを確認することで、安全に型を判定しています。

カスタムタイプガードを使う際の注意点

カスタムタイプガードは便利ですが、いくつか注意が必要です。

  • 型の一貫性: カスタムタイプガード関数が返す型情報はTypeScript全体で一貫性を保つため、型定義の変更や関数のロジックを変更した場合は、すべての関連箇所を再確認することが重要です。
  • 複雑な条件は避ける : カスタムタイプガード内で条件を複雑にしすぎると、型推論が混乱し、かえって保守性が低下します。できる限りシンプルな条件を心がけましょう。
  • ユーザー定義型と外部データの整合性: 外部APIのデータを検証する場合、カスタムタイプガードがしっかりとデータの整合性を確保しているかを確認する必要があります。

まとめ

TypeScriptのカスタムタイプガードを活用することで、型の安全性を保ちながら、条件に応じた柔軟なデータ処理が可能になります。特に、ユニオン型やAPIレスポンスの判定など、型が確定していないデータを扱う場面で大きな効果を発揮します。これらの技術を活用して、より堅牢で型安全なTypeScriptコードを実現しましょう。

Recommend