TypeScriptにおいて、型システムはコードの安全性と可読性を大幅に向上させる強力なツールです。特にkeyofと条件型を組み合わせることで、より柔軟かつ高度な型操作が可能になります。このような技術を理解し活用することで、複雑なオブジェクトやデータ構造に対する厳密な型チェックを実現でき、エラーの発見が早まり、保守性も向上します。本記事では、keyofと条件型を駆使したインデックス型操作について、基本的な概念から実践的な応用方法まで、ステップバイステップで解説していきます。
keyofとは何か
TypeScriptにおいて、keyof
はオブジェクト型のキーを列挙する型演算子です。keyof
を使用すると、オブジェクトのプロパティ名(キー)の型を取得することができ、これによりプロパティの名前に基づいて型安全な操作が可能になります。例えば、あるオブジェクト型Person
がname
やage
というプロパティを持つ場合、keyof Person
はそのキーである"name" | "age"
のユニオン型を返します。
基本構文
keyof
の基本的な構文は次の通りです。
type Person = {
name: string;
age: number;
};
type PersonKeys = keyof Person; // "name" | "age"
このように、keyof
は型のプロパティ名を列挙し、ユニオン型として返します。これにより、特定のプロパティに対する型安全な操作やアクセスが可能となります。
条件型とは何か
条件型(Conditional Types)とは、TypeScriptにおける型の条件分岐を可能にする強力な機能です。これは、式のように型を動的に変えるための構文で、条件によって異なる型を返すことができます。具体的には、条件に基づいて異なる型を選択することで、より柔軟な型定義が可能になります。
基本構文
条件型の基本的な構文は以下のようになります。
T extends U ? X : Y
ここで、T
がU
に代入可能(extends
で表現)であれば型X
が返され、そうでない場合は型Y
が返されます。この仕組みにより、型の評価を動的に行うことができるのです。
例
以下の例は、T
がstring
型であればtrue
を、そうでなければfalse
を返す条件型です。
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
このように、条件型を使うことで、型の制約や状況に応じて型の動的な切り替えができ、TypeScriptでの型定義をより強力にコントロールできます。
keyofと条件型の組み合わせ方
TypeScriptにおいて、keyof
と条件型を組み合わせることで、さらに柔軟で強力な型操作が可能になります。この組み合わせにより、オブジェクトのプロパティに基づいた動的な型チェックや操作が実現でき、より安全かつ高度なプログラムが構築できます。
keyofと条件型を使った基本例
まず、keyof
でオブジェクトのキーを取得し、そのキーの型を条件によって変える例を見てみましょう。
type Person = {
name: string;
age: number;
isActive: boolean;
};
type KeyValue<T, K extends keyof T> = K extends string ? string : T[K];
type Test1 = KeyValue<Person, "name">; // string
type Test2 = KeyValue<Person, "age">; // number
type Test3 = KeyValue<Person, "isActive">; // boolean
この例では、KeyValue
型はkeyof
で取り出したキーK
がstring
型であればstring
を返し、それ以外の場合はそのキーに対応する元の型を返します。これにより、型の厳密な制御が可能です。
keyofと条件型の組み合わせによるユースケース
特定のキーが存在するかどうかを条件型でチェックし、そのキーに応じて動的に型を決定するようなケースもよくあります。例えば、オブジェクトのプロパティがオプショナルである場合や、型に依存した処理が必要な場合です。
type OptionalPropertyCheck<T, K extends keyof T> = undefined extends T[K] ? true : false;
type PersonHasAge = OptionalPropertyCheck<Person, "age">; // false
type PersonHasMiddleName = OptionalPropertyCheck<{ name: string, middleName?: string }, "middleName">; // true
このように、keyof
と条件型の組み合わせを使えば、型の存在を条件としてより高度な型の操作ができるため、複雑な型定義にも対応できるのです。
インデックス型操作の実践例
keyof
と条件型を組み合わせたインデックス型の操作は、TypeScriptの型システムを活用する際の非常に有用なテクニックです。この組み合わせを使用することで、オブジェクト型の特定のプロパティにアクセスし、動的に型を変換したり、型安全な操作を行うことができます。
基本的なインデックス型操作
まず、基本的なインデックス型の操作を見てみましょう。keyof
を使用してオブジェクトのプロパティにアクセスし、型を取得する方法です。
type Person = {
name: string;
age: number;
isActive: boolean;
};
type PersonProperty<T, K extends keyof T> = T[K];
type NameType = PersonProperty<Person, "name">; // string
type AgeType = PersonProperty<Person, "age">; // number
type IsActiveType = PersonProperty<Person, "isActive">; // boolean
この例では、PersonProperty
という型を定義して、keyof
で取り出したキーK
に基づいてそのプロパティの型を取得しています。これにより、オブジェクトのプロパティに対する型安全な操作が可能になります。
インデックス型の変換
次に、keyof
と条件型を組み合わせて、オブジェクトのプロパティに基づいて型を変換する例を見てみます。たとえば、プロパティがstring
型の場合にnumber
に変換し、それ以外はそのままの型を保持するようにします。
type ConvertStringToNumber<T> = {
[K in keyof T]: T[K] extends string ? number : T[K];
};
type PersonConverted = ConvertStringToNumber<Person>;
// PersonConvertedは次の型になります:
// {
// name: number;
// age: number;
// isActive: boolean;
// }
この例では、ConvertStringToNumber
という型を定義し、keyof
で取得したプロパティの型がstring
の場合にnumber
に変換しています。これにより、型に応じた動的な変換が可能となります。
オブジェクト型のフィルタリング
さらに、keyof
と条件型を使って、特定の型に基づいたプロパティのみを抽出することもできます。例えば、オブジェクトからstring
型のプロパティのみを抽出する場合です。
type FilterByType<T, U> = {
[K in keyof T]: T[K] extends U ? K : never;
}[keyof T];
type StringKeys = FilterByType<Person, string>; // "name"
この例では、FilterByType
という型を定義し、オブジェクトのプロパティがU
型(ここではstring
)に一致するものだけを抽出します。このようなテクニックにより、特定の型に基づいてオブジェクトを動的に操作できます。
これらのインデックス型操作の実践例を活用することで、型安全性を維持しながら、TypeScriptで柔軟かつ強力な型定義が可能となります。
インデックス型のネストとその応用
TypeScriptでは、複雑なデータ構造を扱う際に、ネストされたインデックス型を操作することが求められます。keyof
と条件型を駆使することで、ネストされたオブジェクトのプロパティにも柔軟にアクセスし、型を操作することが可能です。ここでは、ネストされたインデックス型の操作とその応用について解説します。
ネストされたオブジェクト型へのアクセス
ネストされたオブジェクトに対してもkeyof
を使用して型安全にアクセスすることができます。次の例では、ネストされた型に対するプロパティの取得方法を示します。
type Address = {
city: string;
postalCode: number;
};
type Person = {
name: string;
age: number;
address: Address;
};
type PersonAddressCityType = Person["address"]["city"]; // string
type PersonAddressPostalCodeType = Person["address"]["postalCode"]; // number
この例では、Person
型の中にAddress
型がネストされています。Person["address"]["city"]
というインデックス型を使うことで、ネストされたプロパティに型安全にアクセスできます。
ネストされたインデックス型の変換
さらに、ネストされたプロパティに対して条件型を適用し、特定の型に基づいて変換することも可能です。次の例では、address
オブジェクト内のstring
型プロパティをnumber
に変換します。
type ConvertNestedStringToNumber<T> = {
[K in keyof T]: T[K] extends object ? ConvertNestedStringToNumber<T[K]> : T[K] extends string ? number : T[K];
};
type ConvertedPerson = ConvertNestedStringToNumber<Person>;
// ConvertedPersonの型:
// {
// name: number;
// age: number;
// address: {
// city: number;
// postalCode: number;
// };
// }
この例では、ネストされたaddress
オブジェクトの中のstring
型プロパティcity
がnumber
に変換されています。ネストされた型にも再帰的に処理を適用できる点が重要です。
ネストされたオブジェクトの一部のプロパティを取得
また、ネストされたプロパティの中から、特定の型に基づいてプロパティ名を抽出することもできます。例えば、Person
型の中からnumber
型のプロパティ名をすべて抽出する場合です。
type ExtractNestedNumberKeys<T> = {
[K in keyof T]: T[K] extends number ? K : T[K] extends object ? ExtractNestedNumberKeys<T[K]> : never;
}[keyof T];
type NumberKeys = ExtractNestedNumberKeys<Person>; // "age" | "postalCode"
この例では、ネストされたオブジェクトからnumber
型のプロパティ名"age"
と"postalCode"
を抽出しています。このように、型に基づいたネストされたプロパティの操作も、keyof
と条件型を組み合わせることで柔軟に行えます。
実践的な応用例
ネストされた型操作の実践的な応用として、JSONデータや複雑なAPIレスポンスを扱う際に役立ちます。例えば、APIから取得したネストされたオブジェクトデータをTypeScriptの型システムを利用して検証し、安全に操作することができます。
このように、keyof
と条件型を組み合わせてネストされたインデックス型を操作することで、複雑なデータ構造を効率的に管理でき、型安全なコードを実現することが可能になります。
ジェネリクスとkeyofの併用方法
TypeScriptでジェネリクスとkeyof
を併用することで、汎用的で再利用可能な型を作成でき、柔軟性の高い型定義が可能となります。特に、ジェネリクスによって型を動的に受け渡しつつ、keyof
を使ってその型のキーを安全に操作することができます。この技術により、複雑な型の制約や条件を実現できます。
基本的なジェネリクスとkeyofの併用
まず、ジェネリクスとkeyof
を組み合わせた基本例を見てみましょう。ジェネリクスを用いて、渡されたオブジェクト型のプロパティを制約し、その型のキーに基づいて操作を行います。
type GetProperty<T, K extends keyof T> = T[K];
type Person = {
name: string;
age: number;
};
type NameType = GetProperty<Person, "name">; // string
type AgeType = GetProperty<Person, "age">; // number
この例では、ジェネリック型T
で渡された型Person
に対して、そのキーをK
として受け取り、T[K]
によってそのキーの型を取得しています。これにより、柔軟に異なるオブジェクト型に対応することができます。
ジェネリクスを使った動的な型制約
次に、ジェネリクスを使って動的な型制約を適用する例です。ジェネリクスにより、特定のプロパティの型に基づいた制約を動的に設定できます。
type OnlyStrings<T> = {
[K in keyof T]: T[K] extends string ? K : never;
}[keyof T];
type Person = {
name: string;
age: number;
isActive: boolean;
};
type StringKeys = OnlyStrings<Person>; // "name"
ここでは、ジェネリクス型OnlyStrings
を使用して、Person
型の中でstring
型のプロパティ名だけを抽出しています。keyof
とジェネリクスの併用により、動的かつ柔軟に型を制約できる強力な手法です。
ジェネリクスと条件型の組み合わせ
ジェネリクスと条件型を組み合わせると、さらに複雑な型の制約や操作が可能になります。例えば、プロパティがstring
型ならある型を、number
型なら別の型を返すような処理をジェネリクスで定義できます。
type TransformProperty<T, K extends keyof T> = T[K] extends string ? `${T[K]} transformed` : T[K];
type Person = {
name: string;
age: number;
};
type TransformedName = TransformProperty<Person, "name">; // "string transformed"
type TransformedAge = TransformProperty<Person, "age">; // number
この例では、TransformProperty
型は、ジェネリック型T
からキーK
を受け取り、キーに対応する型がstring
の場合はその値を変換し、それ以外は元の型を保持しています。このような条件型とジェネリクスを組み合わせたパターンは、柔軟かつ高度な型操作を可能にします。
複雑なジェネリクスの応用例
ジェネリクスとkeyof
の併用は、APIレスポンスの型を安全に扱う場面で特に有効です。たとえば、あるAPIから返されるJSONデータがどのような構造であっても、その構造に基づいて安全に操作するための型を事前に定義できます。
type ApiResponse<T> = {
data: T;
error?: string;
};
type GetApiData<T> = ApiResponse<T>["data"];
type User = {
id: number;
name: string;
};
type UserData = GetApiData<User>; // User型
この例では、ApiResponse
という汎用的な型に対して、GetApiData
というジェネリクスを用いてdata
部分の型を取得しています。これにより、異なるAPIレスポンスにも対応できる柔軟な型定義が可能です。
ジェネリクスとkeyof
の併用によって、TypeScriptの型システムをより高度に活用し、動的かつ型安全なコードが実現できます。これにより、複雑な型操作が必要な場面でも、堅牢で保守性の高いコードを構築できるのです。
オプションプロパティの扱い方
TypeScriptでは、オプションプロパティ(?
を使って定義されるプロパティ)は、存在するかどうかわからない場合に便利です。しかし、オプションプロパティを扱う際には、型安全性を保ちながら動的にプロパティの有無をチェックし、それに応じた操作を行う必要があります。keyof
や条件型を組み合わせることで、オプションプロパティの存在を確認し、適切な型操作を行う方法を見ていきます。
オプションプロパティの基本的な扱い方
オプションプロパティとは、定義時にそのプロパティが存在するかどうかを明示せずに型を定義できる機能です。次のように定義します。
type Person = {
name: string;
age?: number; // ageはオプション
};
const person1: Person = { name: "Alice" }; // ageがなくてもOK
const person2: Person = { name: "Bob", age: 25 }; // ageがあってもOK
ここで、age
はオプションプロパティです。Person
型のインスタンスではage
があってもなくても良いということが定義されています。
オプションプロパティの存在確認
keyof
と条件型を使うことで、オプションプロパティかどうかを型で確認できます。例えば、プロパティがオプションかどうかを判定し、その結果に応じて型を変更するような型を作成することが可能です。
type IsOptional<T, K extends keyof T> = undefined extends T[K] ? true : false;
type Person = {
name: string;
age?: number;
};
type IsAgeOptional = IsOptional<Person, "age">; // true
type IsNameOptional = IsOptional<Person, "name">; // false
この例では、IsOptional
という条件型を使用して、age
がオプションプロパティであることを確認しています。undefined extends T[K]
の式によって、T[K]
がundefined
型を含むかどうかを判定し、それに応じてtrue
またはfalse
を返しています。
オプションプロパティを含む型の動的操作
オプションプロパティを動的に操作したい場合、keyof
と条件型を組み合わせることで、存在するプロパティに対してのみ型を適用することが可能です。次の例では、オプションプロパティが存在するかどうかで処理を変える型を定義します。
type OptionalPropertyHandler<T> = {
[K in keyof T]: T[K] extends undefined ? never : T[K];
};
type Person = {
name: string;
age?: number;
};
type PersonWithRequiredProps = OptionalPropertyHandler<Person>;
// PersonWithRequiredPropsの型:
// {
// name: string;
// age: number | undefined;
// }
この例では、OptionalPropertyHandler
型を使用して、age
がオプションプロパティであるため、その値がundefined
を含む可能性があることを示しています。これにより、型安全な操作を行うことができます。
オプションプロパティの実践例
オプションプロパティの実践的な応用として、APIから受け取ったデータにオプションプロパティが含まれるケースがよくあります。この場合、オプションプロパティをチェックしながら、安全にデータを操作するための型を定義しておくと、コードの堅牢性が向上します。
type ApiResponse<T> = {
data?: T;
error?: string;
};
type User = {
id: number;
name: string;
};
function handleApiResponse(response: ApiResponse<User>) {
if (response.data) {
console.log(response.data.name);
} else if (response.error) {
console.log(response.error);
}
}
このように、オプションプロパティを含むデータ構造を処理する際には、keyof
や条件型を駆使することで、安全かつ型に基づいたデータ操作が可能になります。
オプションプロパティを適切に扱うことで、コードの柔軟性と堅牢性を高めることができ、予期せぬエラーを防ぎつつ、型安全な操作を維持できます。
keyofを使った型の安全性向上
TypeScriptの型システムを活用することで、コードの安全性を大幅に向上させることができます。その中でもkeyof
は、オブジェクトのプロパティ名を取得し、型の安全性を保証するための非常に有用なツールです。keyof
を利用することで、誤ったプロパティへのアクセスや型のミスマッチを防ぎ、コードの信頼性を高めることが可能です。
型安全なプロパティアクセス
keyof
を使うことで、オブジェクト型のプロパティ名に対して型安全なアクセスを行うことができます。次の例では、keyof
を用いて安全にプロパティにアクセスする方法を示します。
type Person = {
name: string;
age: number;
};
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person: Person = { name: "Alice", age: 30 };
const name = getProperty(person, "name"); // string
const age = getProperty(person, "age"); // number
この例では、getProperty
関数がジェネリクスとkeyof
を使用してオブジェクトのプロパティに型安全にアクセスしています。関数に渡されたキーがオブジェクトのプロパティとして有効であることが型チェックされ、間違ったプロパティ名を指定した場合にはコンパイル時にエラーが発生します。
// コンパイルエラー: "height"は"Person"型のプロパティではない
const height = getProperty(person, "height");
このように、keyof
を使うことで、誤ったプロパティへのアクセスを防ぎ、型の安全性を確保できます。
動的プロパティアクセスにおける型安全性
JavaScriptでは、動的にプロパティ名を指定してオブジェクトの値にアクセスすることが一般的です。しかし、このアプローチでは、存在しないプロパティにアクセスしてしまう危険性があります。TypeScriptではkeyof
を利用することで、動的なプロパティアクセスも型安全に行えます。
type Car = {
make: string;
model: string;
year: number;
};
function printProperty<T, K extends keyof T>(obj: T, key: K): void {
console.log(`The value of ${key} is ${obj[key]}`);
}
const car: Car = { make: "Toyota", model: "Corolla", year: 2020 };
printProperty(car, "make"); // OK: "make"は有効なプロパティ
printProperty(car, "year"); // OK: "year"も有効なプロパティ
// printProperty(car, "color"); // エラー: "color"はCar型に存在しない
このように、keyof
を使うことで、動的なプロパティ名に対しても型安全なアクセスを行うことができ、実行時に発生しうるエラーを事前に防ぐことができます。
オブジェクトの型を制約するユースケース
keyof
はオブジェクトのプロパティ名を型レベルで操作できるため、特定のプロパティに制約を加えたり、型安全なロジックを実現する場面で非常に有効です。次の例では、keyof
を用いて、特定のプロパティ名が有効な場合にのみ操作を許可する型を定義しています。
type UpdatePerson<T, K extends keyof T> = {
[P in K]: T[P];
};
type Person = {
name: string;
age: number;
};
type UpdatedPerson = UpdatePerson<Person, "name">; // { name: string }
この例では、UpdatePerson
型が、Person
型の中からname
プロパティのみを抽出しています。keyof
を使用することで、特定のプロパティのみを操作したり、型を制約するロジックを安全に実装することが可能です。
型の安全性向上によるエラーの削減
keyof
を使った型の安全性向上は、特に大規模なプロジェクトや複雑なデータ構造を扱う際に役立ちます。誤ったプロパティアクセスによるエラーを未然に防ぐことで、バグの発生を大幅に減らし、コードの品質を向上させることができます。
TypeScriptの型システムを最大限に活用することで、特にkeyof
や条件型を使ったプロパティの操作は、コードの信頼性と可読性を大きく向上させる重要な手法です。
実践で使えるテクニック
keyof
と条件型を組み合わせた型操作は、TypeScriptの強力な型システムを活用するための基本的なツールです。これらを使うことで、より型安全で柔軟なコードを実装できるだけでなく、開発の効率を大幅に向上させることができます。ここでは、実際のプロジェクトで役立つテクニックをいくつか紹介します。
オブジェクトの部分型操作
オブジェクト型の一部のプロパティだけを操作したい場合、keyof
を使って部分的にプロパティを抽出するテクニックがあります。これにより、既存の型から必要なプロパティのみを取り出して再利用できます。
type Person = {
name: string;
age: number;
isActive: boolean;
};
type PartialPerson<T, K extends keyof T> = {
[P in K]: T[P];
};
type PersonNameAndAge = PartialPerson<Person, "name" | "age">;
// { name: string; age: number; }
このように、PartialPerson
を使用して、Person
型の中からname
とage
プロパティのみを取り出して新しい型を作成できます。部分型操作は、大規模なオブジェクトを扱う際に非常に有用です。
オプショナルなプロパティを厳密に操作する
オプションプロパティ(?
)を含むオブジェクトでは、プロパティが存在するかどうかを型レベルで確認しつつ、型安全な操作を行うことが求められます。次の例では、keyof
と条件型を利用して、オプショナルなプロパティにのみ特定の処理を行う方法を示します。
type Optional<T> = {
[K in keyof T]: undefined extends T[K] ? K : never;
}[keyof T];
type Person = {
name: string;
age?: number;
isActive: boolean;
};
type OptionalKeys = Optional<Person>; // "age"
このテクニックを使うと、Person
型の中からオプショナルなプロパティだけを抽出できます。このように、条件型を活用することで、プロパティの存在や型に基づいた動的な操作が可能です。
動的なプロパティの設定
プロパティ名や値が動的に決まる場合も、keyof
を使用して型安全に動的なプロパティ操作が行えます。次の例では、keyof
を使って、オブジェクトに対して動的に値を設定する方法を示します。
function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
obj[key] = value;
}
type Car = {
make: string;
model: string;
year: number;
};
const car: Car = { make: "Toyota", model: "Corolla", year: 2020 };
setProperty(car, "year", 2021); // OK: yearはnumber型
setProperty(car, "make", "Honda"); // OK: makeはstring型
// setProperty(car, "model", 2021); // エラー: modelはstring型なのにnumberを渡している
このように、プロパティ名と値が動的に決まる場合でも、keyof
を利用することで型安全にプロパティを操作できます。これにより、誤った値の設定を未然に防ぐことが可能です。
マッピング型を使ったプロパティの変換
keyof
とマッピング型を組み合わせて、オブジェクトのプロパティを動的に変換することも可能です。次の例では、オブジェクト内のすべてのstring
型プロパティをnumber
に変換しています。
type ConvertStringsToNumbers<T> = {
[K in keyof T]: T[K] extends string ? number : T[K];
};
type Person = {
name: string;
age: number;
isActive: boolean;
};
type ConvertedPerson = ConvertStringsToNumbers<Person>;
// ConvertedPersonの型は次のようになります:
// { name: number; age: number; isActive: boolean; }
この例では、ConvertStringsToNumbers
型を定義して、Person
型のすべてのstring
プロパティをnumber
に変換しています。動的にプロパティの型を変換するこのテクニックは、オブジェクトの操作や型変換を柔軟に行うための強力な手段です。
TypeScriptでの型安全なAPIレスポンス操作
実際の開発では、APIレスポンスに基づいたデータ操作が頻繁に発生します。keyof
や条件型を使えば、受け取ったレスポンスデータに基づいて安全にデータを操作することができます。
type ApiResponse<T> = {
data?: T;
error?: string;
};
type User = {
id: number;
name: string;
};
function handleApiResponse(response: ApiResponse<User>) {
if (response.data) {
console.log(`User ID: ${response.data.id}`);
} else if (response.error) {
console.log(`Error: ${response.error}`);
}
}
このように、APIレスポンスを型で制約することで、型安全なデータ操作が可能になり、実行時のエラーを未然に防ぐことができます。
これらのテクニックを駆使すれば、実践的なプロジェクトでkeyof
と条件型を効率よく利用でき、開発の効率やコードの保守性が向上します。
演習問題
keyof
や条件型の理解を深めるために、いくつかの演習問題を用意しました。これらの問題を解くことで、実践的なスキルを養い、TypeScriptの高度な型操作をより深く理解することができます。各問題に対する解答も提供していますので、自分のコードと比較しながら進めてください。
問題1: オブジェクトのプロパティを取得する関数
次の関数getProperty
は、オブジェクトのプロパティを取得する関数です。この関数が型安全に動作するように、keyof
を使用して型定義を完成させてください。
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
type Person = {
name: string;
age: number;
};
const person: Person = { name: "Alice", age: 25 };
// ここでgetPropertyを使って"age"を取得してください
const age = getProperty(person, "age"); // number型の値を取得
解答のポイント:
keyof
を使用して、key
がPerson
型の有効なプロパティであることを保証する。T[K]
によって、指定されたプロパティの型を取得する。
問題2: オプショナルなプロパティのチェック
次のisOptional
型を完成させ、指定されたプロパティがオプショナルであるかどうかを判定できるようにしてください。
type Person = {
name: string;
age?: number;
};
type IsOptional<T, K extends keyof T> = undefined extends T[K] ? true : false;
type Test1 = IsOptional<Person, "age">; // true
type Test2 = IsOptional<Person, "name">; // false
解答のポイント:
- 条件型
undefined extends T[K]
を使うことで、指定されたプロパティがundefined
を含むかどうかを判定する。
問題3: すべての`string`プロパティを`number`に変換
次のConvertStringsToNumbers
型を完成させ、オブジェクト内のすべてのstring
プロパティをnumber
に変換してください。
type ConvertStringsToNumbers<T> = {
[K in keyof T]: T[K] extends string ? number : T[K];
};
type Person = {
name: string;
age: number;
isActive: boolean;
};
type ConvertedPerson = ConvertStringsToNumbers<Person>;
// ConvertedPersonの型は次のようになります:
// { name: number; age: number; isActive: boolean; }
解答のポイント:
- マッピング型と条件型を使用して、各プロパティが
string
であればnumber
に変換する。
問題4: 指定されたプロパティのみを抽出
次のPickProperties
型を完成させ、指定されたプロパティのみを抽出する型を作成してください。
type PickProperties<T, K extends keyof T> = {
[P in K]: T[P];
};
type Person = {
name: string;
age: number;
isActive: boolean;
};
type PersonNameAndAge = PickProperties<Person, "name" | "age">;
// { name: string; age: number; }
解答のポイント:
Pick
型を模倣する形で、指定されたプロパティのみを取り出す型を作成する。
問題5: プロパティの存在チェック
次のHasProperty
型を完成させ、オブジェクト型が指定されたプロパティを持っているかどうかを判定する型を作成してください。
type HasProperty<T, K extends string> = K extends keyof T ? true : false;
type Person = {
name: string;
age: number;
};
type Test1 = HasProperty<Person, "name">; // true
type Test2 = HasProperty<Person, "height">; // false
解答のポイント:
K extends keyof T
を使用して、指定されたプロパティが存在するかどうかを判定する。
これらの演習問題を通じて、keyof
や条件型の実践的な使い方を学び、TypeScriptにおける型安全なコーディングに慣れていきましょう。演習を繰り返すことで、これらのテクニックが実際の開発でも使えるスキルとして身に付きます。
まとめ
本記事では、TypeScriptにおけるkeyof
と条件型を組み合わせた高度なインデックス型操作について解説しました。keyof
による型安全なプロパティアクセスや、条件型を利用した柔軟な型操作を学ぶことで、型システムを最大限に活用し、エラーの少ない堅牢なコードを実現できます。また、実践的なテクニックや演習問題を通じて、これらの概念を深く理解し、実際の開発での応用方法も確認しました。これらの知識を活用して、より型安全でメンテナンス性の高いコードを書いていきましょう。
コメント