【初心者向け】TypeScriptでプロパティ型を動的に解決する方法をわかりやすく解説
TypeScript で別のプロパティ値に基づいてプロパティ型を動的に解決する方法
ジェネリック型を使用する
ジェネリック型を使用すると、プロパティの型をパラメータとして渡すことができます。 その後、パラメータを使用して、他のプロパティの型を動的に定義することができます。
interface User<T> {
id: number;
name: string;
// プロパティ "data" の型は T に基づいて動的に解決される
data: T;
}
const user1: User<string> = {
id: 1,
name: "Taro",
data: "Hello"
};
const user2: User<number> = {
id: 2,
name: "Jiro",
data: 100
};
この例では、User
インターフェースは T
というジェネリック型を持ち、data
プロパティの型を定義します。 T
は、User
インスタンスが作成されるときに渡される実際の型に置き換えられます。 これにより、data
プロパティの型が動的に解決されます。
条件付き型を使用する
条件付き型を使用すると、別のプロパティ値に基づいて、プロパティの型のセットから選択することができます。
type UserRole = "admin" | "user";
interface User {
id: number;
name: string;
role: UserRole;
// "data" プロパティの型は "role" プロパティの値に基づいて動的に解決される
data: {
message: string;
[key: string]: any; // "admin" 以外のロールの場合、追加のプロパティを許可
} & ({ admin: true } extends { admin: true } & User ? { adminData: string } : {});
}
const adminUser: User = {
id: 1,
name: "Taro",
role: "admin",
data: {
message: "Hello from admin",
adminData: "This is admin-only data"
}
};
const normalUser: User = {
id: 2,
name: "Jiro",
role: "user",
data: {
message: "Hello from user"
}
};
この例では、User
インターフェースは role
プロパティを持ち、その値に基づいて data
プロパティの型を定義します。 &
演算子と extends
キーワードを使用して、条件付きでプロパティを追加します。 admin
ロールの場合は、adminData
プロパティが追加されます。
型ガードを使用する
型ガードを使用すると、ランタイム時にオブジェクトの型を検査し、それに基づいてプロパティの型を動的に定義することができます。
interface Product {
id: number;
name: string;
price: number;
// "details" プロパティの型は "type" プロパティの値に基づいて動的に解決される
details: ProductDetails | ProductReview;
}
interface ProductDetails {
description: string;
stock: number;
}
interface ProductReview {
rating: number;
review: string;
}
function getProductDetails(productId: number): Product {
// ... 商品情報取得処理 ...
const product: Product = {
id: productId,
name: "Product Name",
price: 100,
details: {
// 商品の種類に基づいて "details" プロパティの型を動的に設定
if (product.type === "simple") {
description: "Product description",
stock: 10
} else {
rating: 5,
review: "Great product!"
}
}
};
return product;
}
この例では、Product
インターフェースは type
プロパティを持ち、その値に基づいて details
プロパティの型を定義します。 getProductDetails
関数は、商品情報に基づいて details
プロパティの型を動的に設定します。
interface User<T> {
id: number;
name: string;
// プロパティ "data" の型は T に基づいて動的に解決される
data: T;
}
const user1: User<string> = {
id: 1,
name: "Taro",
data: "Hello"
};
const user2: User<number> = {
id: 2,
name: "Jiro",
data: 100
};
type UserRole = "admin" | "user";
interface User {
id: number;
name: string;
role: UserRole;
// "data" プロパティの型は "role" プロパティの値に基づいて動的に解決される
data: {
message: string;
[key: string]: any; // "admin" 以外のロールの場合、追加のプロパティを許可
} & ({ admin: true } extends { admin: true } & User ? { adminData: string } : {});
}
const adminUser: User = {
id: 1,
name: "Taro",
role: "admin",
data: {
message: "Hello from admin",
adminData: "This is admin-only data"
}
};
const normalUser: User = {
id: 2,
name: "Jiro",
role: "user",
data: {
message: "Hello from user"
}
};
interface Product {
id: number;
name: string;
price: number;
// "details" プロパティの型は "type" プロパティの値に基づいて動的に解決される
details: ProductDetails | ProductReview;
}
interface ProductDetails {
description: string;
stock: number;
}
interface ProductReview {
rating: number;
review: string;
}
function getProductDetails(productId: number): Product {
// ... 商品情報取得処理 ...
const product: Product = {
id: productId,
name: "Product Name",
price: 100,
details: {
// 商品の種類に基づいて "details" プロパティの型を動的に設定
if (product.type === "simple") {
description: "Product description",
stock: 10
} else {
rating: 5,
review: "Great product!"
}
}
};
return product;
}
これらの例は、それぞれの方法の基本的な使い方を示しています。 実際の使用例では、より複雑な型構造やロジックが必要になる場合があります。
- 型ガードを使用する場合は、ランタイム時に型検査を行うため、パフォーマンス上の影響を考慮する必要があります。
- 上記のコードは TypeScript バージョン 4.1 を使用しています。
マッピング型を使用する
マッピング型を使用すると、既存の型から新しい型を生成することができます。 新しい型のプロパティの型は、元の型のプロパティの型に基づいて動的に定義することができます。
type ProductDetailsMap = {
simple: ProductDetails;
advanced: ProductReview;
};
type Product<T extends keyof ProductDetailsMap> = {
id: number;
name: string;
price: number;
// "details" プロパティの型は T に基づいて動的に解決される
details: ProductDetailsMap[T];
};
const simpleProduct: Product<"simple"> = {
id: 1,
name: "Simple Product",
price: 50,
details: {
description: "Simple product description",
stock: 10
}
};
const advancedProduct: Product<"advanced"> = {
id: 2,
name: "Advanced Product",
price: 100,
details: {
rating: 5,
review: "Great product!"
}
};
この例では、ProductDetailsMap
型を使用して、simple
と advanced
という 2 つのプロパティを定義します。 各プロパティの値は、ProductDetails
または ProductReview
インターフェースのいずれかになります。 Product
型はジェネリック型で、T
という型パラメータを持ちます。 T
は、ProductDetailsMap
型のキーのいずれかである必要があります。 details
プロパティの型は、ProductDetailsMap[T]
型として定義されます。 これは、T
に基づいて動的に選択される ProductDetails
または ProductReview
インターフェースに対応します。
型エイリアスを使用する
type ProductDetailsAlias<T extends "simple" | "advanced"> = {
[key in T]: {
// プロパティの型は T に基づいて動的に解決される
[key: string]: any;
}
}[T];
type Product<T extends "simple" | "advanced"> = {
id: number;
name: string;
price: number;
// "details" プロパティの型は T に基づいて動的に解決される
details: ProductDetailsAlias<T>;
};
const simpleProduct: Product<"simple"> = {
id: 1,
name: "Simple Product",
price: 50,
details: {
simple: {
description: "Simple product description",
stock: 10
}
}
};
const advancedProduct: Product<"advanced"> = {
id: 2,
name: "Advanced Product",
price: 100,
details: {
advanced: {
rating: 5,
review: "Great product!"
}
}
};
この例は、ProductDetailsAlias
型エイリアスを使用して、simple
と advanced
という 2 つのプロパティを定義します。 各プロパティの値は、オブジェクトリテラルになります。 オブジェクトリテラル内のプロパティの型は、T
に基づいて動的に定義されます。 Product
型は、ProductDetailsAlias<T>
型エイリアスを使用して、details
プロパティの型を定義します。
LookUp 型を使用する
LookUp 型を使用すると、オブジェクトまたはユニオン型から特定のプロパティにアクセスすることができます。 プロパティの型は、ランタイム時に動的に解決されます。
type ProductDetailsLookUp<T extends "simple" | "advanced"> = LookUp<ProductDetailsMap, T>;
type Product<T extends "simple" | "advanced"> = {
id: number;
name: string;
price: number;
// "details" プロパティの型は T に基づいて動的に解決される
details: ProductDetailsLookUp<
typescript