TypeScriptは、静的型付け言語でありながら、柔軟な型推論機能を提供しているため、コードの安全性とメンテナンス性を向上させることができます。その中でも、keyof
やtypeof
といった演算子は、開発者が型情報を動的に取得し、型安全なコードを書く上で非常に重要です。本記事では、keyof
とtypeof
を使って、より効率的で安全な型推論を行う方法をわかりやすく解説します。
型推論とは何か
TypeScriptの型推論とは、開発者が明示的に型を指定しなくても、コンパイラが自動的に型を推測してくれる機能です。これにより、コードが簡潔になり、書きやすくなるだけでなく、バグの発見が早まるという利点があります。たとえば、変数に値を代入した際、その値に基づいて自動的に型が決定されます。型推論は、TypeScriptの強力な型システムを活用するための基盤であり、開発者が意識せずに安全なコードを書くことを可能にします。
型推論の重要性
型推論を活用することで、以下のメリットが得られます。
- コードの読みやすさが向上する
- 型安全性が保たれ、潜在的なエラーを防止できる
- 冗長な型宣言が不要になるため、コード量が減少する
型推論は、TypeScriptの強力な特徴の一つであり、特に大規模なプロジェクトにおいて、可読性と保守性を高める重要な役割を果たします。
`keyof`演算子の基本
keyof
演算子は、TypeScriptでオブジェクトのキー名を型として取得するために使用される演算子です。オブジェクト型のすべてのキーをユニオン型として返し、型安全なコードを書くのに役立ちます。たとえば、あるオブジェクトのプロパティ名だけを指定したいときに、keyof
を使うことでそのオブジェクトに存在しないキーを指定するミスを防ぐことができます。
`keyof`のシンプルな例
以下のようなオブジェクト型に対してkeyof
を使うと、オブジェクトのキーが型として取得できます。
type Person = {
name: string;
age: number;
};
type PersonKeys = keyof Person; // "name" | "age"
このように、PersonKeys
は "name"
または "age"
という文字列型を持つユニオン型になります。この仕組みにより、間違ったキー名を使わないよう型チェックが強化されます。
型安全なプロパティ参照の実現
keyof
を使うことで、オブジェクトのプロパティを型安全に参照できます。例えば、以下のように汎用的な関数を作ることが可能です。
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: "John", age: 30 };
const name = getProperty(person, "name"); // 型安全に "name" プロパティを取得
このように、keyof
演算子を使うと型安全にオブジェクトのプロパティにアクセスでき、バグを減らすことが可能になります。
`keyof`を使った型安全なコードの実装例
keyof
演算子を利用することで、型安全なコードを書くための強力な仕組みが得られます。特に、オブジェクトのプロパティにアクセスする際に、存在しないキーを指定してエラーが発生するのを防ぐことが可能です。これにより、コードの安定性が向上し、バグの発生を抑えることができます。
型安全なオブジェクト操作の実装
例えば、keyof
を使ってオブジェクトのプロパティを設定する型安全な関数を実装してみましょう。以下の例では、setProperty
関数が特定のキーに基づいてオブジェクトの値を更新しますが、指定するキーが必ず存在することをkeyof
を通じて保証しています。
function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
obj[key] = value;
}
const user = { name: "Alice", age: 25 };
// 型安全にプロパティを更新
setProperty(user, "name", "Bob"); // OK
setProperty(user, "age", 30); // OK
この関数では、keyof
を利用して、obj
のキー名が必ず有効なものであることをコンパイル時にチェックできます。存在しないキーを使おうとするとエラーが発生するため、開発者は安心してコードを記述できます。
オブジェクトのプロパティチェックを行う例
また、keyof
を使ってオブジェクトに指定されたキーが存在するかをチェックする関数も簡単に実装できます。以下のように、keyof
を利用することで型安全なキーの存在確認を行うことができます。
function hasKey<T>(obj: T, key: keyof T): boolean {
return key in obj;
}
const product = { id: 1, name: "Laptop", price: 1000 };
console.log(hasKey(product, "name")); // true
console.log(hasKey(product, "color")); // エラー: "color"は存在しないプロパティ
このように、keyof
を使えば、オブジェクト操作を型安全に実装できるだけでなく、コードの信頼性を向上させることが可能です。
`typeof`演算子の基本
typeof
演算子は、JavaScriptと同様にTypeScriptでも使用できる演算子で、変数や値の型を取得する際に利用されます。ただし、TypeScriptではtypeof
が型レベルで使えるため、実行時に変数の型を取得するだけでなく、型推論に活用することができます。これにより、コードの型チェックや型推論が柔軟に行えるようになり、より安全で効率的なコードを書くことができます。
`typeof`のシンプルな例
まずは、基本的なtypeof
の使い方を見てみましょう。以下の例では、ある変数の型をtypeof
で取得し、その型を他の変数や関数に適用します。
let userName = "Alice";
let userAge = 30;
// `typeof`で取得した型を利用
let anotherUserName: typeof userName; // string型として推論される
anotherUserName = "Bob"; // OK
この例では、userName
の型がstring
であることをtypeof
を用いて取得し、その型をanotherUserName
に適用しています。こうすることで、明示的に型を指定する必要がなくなり、型安全性が保たれます。
関数における`typeof`の活用
typeof
は関数の引数や返り値の型を推論する際にも非常に有効です。特に、変数の型を動的に利用する場合や、複雑な型を再利用する場合に便利です。
const getUser = () => {
return { name: "Alice", age: 30 };
};
type User = ReturnType<typeof getUser>; // 関数の返り値の型を取得
const newUser: User = { name: "Bob", age: 25 }; // 型安全なオブジェクト
この例では、ReturnType
とtypeof
を組み合わせて、関数getUser
の返り値の型を取得しています。これにより、関数の返り値に基づいて型を再利用することが可能になり、コードの保守性が向上します。
定数オブジェクトの型取得
さらに、typeof
は定数オブジェクトの型を取得する場合にも使われます。特定のオブジェクトの型をそのまま他の変数や関数に適用したい場合に、typeof
は非常に便利です。
const config = {
apiUrl: "https://api.example.com",
timeout: 5000,
};
type Config = typeof config;
function setup(config: Config) {
console.log(config.apiUrl, config.timeout);
}
setup(config); // OK
このように、typeof
を使うことで、既存のオブジェクトや変数から型情報を取得し、それを再利用することができ、コードの効率性と安全性が大幅に向上します。
`typeof`を用いた動的な型推論の応用
typeof
演算子は、実行時の型情報を活用して、動的に型を推論することが可能です。これにより、定数や関数の返り値に基づいて、その型を利用した柔軟なコードを記述することができます。特に、複雑なデータ構造やユースケースにおいて、typeof
を活用することで型の再利用や保守性を高めることができます。
オブジェクトリテラルの型推論
TypeScriptでは、typeof
を用いてオブジェクトリテラルの型を推論することで、複数の場所で同じ型を再利用できるようになります。これにより、型の冗長な定義を避け、コードの再利用性を高めることができます。
const settings = {
theme: "dark",
fontSize: 14,
};
type SettingsType = typeof settings;
function updateSettings(newSettings: SettingsType) {
console.log(newSettings.theme, newSettings.fontSize);
}
const updatedSettings: SettingsType = { theme: "light", fontSize: 16 };
updateSettings(updatedSettings); // OK
この例では、settings
オブジェクトの型をtypeof
で取得し、それをupdateSettings
関数や他の変数に適用しています。これにより、型を重複して定義する必要がなくなり、コードの一貫性が保たれます。
条件付き型と`typeof`の組み合わせ
typeof
を条件付き型(conditional types)と組み合わせることで、柔軟な型推論を行うことができます。たとえば、関数の引数によって型を切り替えるような場合に便利です。
function getValue<T>(value: T): T extends string ? string : number {
if (typeof value === "string") {
return value as T extends string ? string : number;
}
return value.toString().length as T extends string ? string : number;
}
const stringValue = getValue("Hello"); // string型が推論される
const numberValue = getValue(12345); // number型が推論される
この例では、getValue
関数が渡された引数の型に応じて、返り値の型が動的に推論されます。typeof
と条件付き型を組み合わせることで、より柔軟な型推論が実現されています。
実行時の型情報を使った汎用的な関数の実装
TypeScriptでは、実行時に得られる型情報を元に汎用的な関数を実装することができます。typeof
を使うことで、実行時に変数や定数の型を動的に取得し、それに基づいて型推論を行うことが可能です。
function processInput(input: typeof settings): void {
if (typeof input.theme === "string") {
console.log(`Theme is set to: ${input.theme}`);
}
if (typeof input.fontSize === "number") {
console.log(`Font size is: ${input.fontSize}`);
}
}
processInput({ theme: "dark", fontSize: 12 }); // OK
この例では、typeof
を使って、渡されたオブジェクトの型を動的に取得し、その型に応じた処理を実行しています。こうすることで、動的な型推論を活用しつつ、安全にコードを記述することができます。
動的な型推論は、特に大規模なプロジェクトや複雑なデータ構造を扱う場合に非常に有用です。typeof
を活用することで、コードの可読性を損なうことなく、型安全性を維持しつつ柔軟なコードを実現できます。
`keyof`と`typeof`を組み合わせた実用的な例
keyof
とtypeof
を組み合わせることで、TypeScriptで型安全かつ柔軟なプログラムを作成することが可能です。これにより、特定のオブジェクトや変数から型情報を動的に取得し、型チェックを強化した効率的なコードを書けます。このセクションでは、両者を組み合わせた実用的な例を見ていきましょう。
`keyof`と`typeof`の組み合わせによる型安全な操作
keyof
でオブジェクトのキーを取得し、typeof
でその型を取得することで、型安全なプロパティ操作が実現できます。次の例では、オブジェクトのキーを動的に取得して操作を行い、その型が厳密にチェックされます。
const user = {
name: "Alice",
age: 30,
};
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
obj[key] = value;
}
// 使用例
const userName = getProperty(user, "name"); // "name"の型はstring
const userAge = getProperty(user, "age"); // "age"の型はnumber
setProperty(user, "name", "Bob"); // 型安全にプロパティを更新
setProperty(user, "age", 35); // 型安全に数値を更新
この例では、keyof
でオブジェクトuser
のキー("name"
または"age"
)を取得し、typeof
でその型を取得して処理しています。これにより、誤った型の値を設定しようとするとコンパイルエラーが発生し、型安全性が保たれます。
動的型の検証と制約
次に、typeof
を使って動的に型を検証し、keyof
と組み合わせて柔軟な処理を実装する例を紹介します。例えば、オブジェクトの特定のキーが文字列である場合にのみ処理を実行するようなコードを書いてみましょう。
function handleProperty<T, K extends keyof T>(obj: T, key: K): void {
const value = obj[key];
if (typeof value === "string") {
console.log(`String value: ${value}`);
} else {
console.log(`Non-string value: ${value}`);
}
}
const product = {
id: 101,
name: "Laptop",
price: 1200,
};
handleProperty(product, "name"); // "String value: Laptop" と出力
handleProperty(product, "price"); // "Non-string value: 1200" と出力
この例では、オブジェクトproduct
のキーを動的に扱い、その値が文字列かどうかをtypeof
で判別し、異なる処理を行っています。これにより、オブジェクトの型に基づいた柔軟なロジックを簡単に記述できます。
ユニオン型と`keyof`、`typeof`の組み合わせ
keyof
とtypeof
を組み合わせることで、ユニオン型を扱うコードでも型安全を保つことができます。たとえば、次の例では、異なるプロパティに対して適切な処理を行う関数を作成しています。
type Car = {
brand: string;
year: number;
};
const myCar: Car = {
brand: "Toyota",
year: 2020,
};
function logCarProperty<K extends keyof Car>(car: Car, key: K): void {
const value = car[key];
if (typeof value === "string") {
console.log(`Car brand: ${value}`);
} else if (typeof value === "number") {
console.log(`Car year: ${value}`);
}
}
logCarProperty(myCar, "brand"); // "Car brand: Toyota"
logCarProperty(myCar, "year"); // "Car year: 2020"
このように、keyof
とtypeof
をうまく活用することで、異なる型のプロパティを持つオブジェクトでも、型安全なコードを記述することが可能になります。keyof
は型のキーを、typeof
はその値の型を動的に取得できるため、柔軟でかつ安全なプログラム設計が実現できます。
このような実装により、型エラーを回避しながら、複雑なデータ構造を扱うコードも効率的に開発できるようになります。
型推論の落とし穴と回避策
TypeScriptの型推論は非常に強力ですが、適切に理解して使用しないと、思わぬエラーやバグを引き起こす可能性があります。特に、keyof
やtypeof
を使用した高度な型推論を行う際には、いくつかの注意点があります。ここでは、型推論におけるよくある落とし穴と、それを避けるための回避策を紹介します。
暗黙的な`any`型の罠
TypeScriptでは、型推論が適切に働かない場合に暗黙的にany
型が割り当てられることがあります。これにより、型安全性が失われ、バグが潜在的に発生する可能性があります。
let value; // 型が指定されていないため、`any`型が割り当てられる
value = "Hello";
value = 123; // 型チェックが行われない
回避策として、初期値を指定するか明示的に型を宣言しておくことが重要です。
let value: string = "Hello"; // これにより、型が明示的に指定される
こうすることで、意図しないany
型の利用を防ぎ、型安全性が確保されます。
動的キーアクセス時の型エラー
keyof
を使用して動的にキーを取得する場合でも、型安全な操作が保証されるわけではありません。オブジェクトのプロパティにアクセスする際に、TypeScriptはすべてのケースで型推論を適切に行えるわけではなく、間違った型推論が行われる場合があります。
const obj = { name: "Alice", age: 30 };
const key = "name"; // keyの型はstringとして推論される
console.log(obj[key]); // エラー: 'string'型はインデックスシグネチャと互換性がない
この場合、key
が"name"
と明示されているにもかかわらず、key
の型がstring
として推論されているため、オブジェクトのプロパティにアクセスできません。回避策としては、キーの型をkeyof
を使って制限する方法があります。
const key: keyof typeof obj = "name"; // これで型安全にキーを指定
console.log(obj[key]); // OK
型の誤推論によるエラー
TypeScriptの型推論は非常に賢明ですが、複雑な型や関数を扱う場合、誤った型推論が行われることがあります。例えば、配列の要素を操作する際、要素の型が推論されるものの、それが予期しない型になる場合があります。
const numbers = [1, 2, 3]; // number[] と推論される
numbers.map(num => num.toFixed(2)); // OK
このような場合は問題ありませんが、複雑なデータ構造を扱う際には、推論された型が期待するものか確認する必要があります。適切な型注釈を追加することで、予期しない型推論を避けられます。
型の広がりに注意
TypeScriptは、可能な限り具体的な型を推論しますが、時には型が広がりすぎてしまうことがあります。たとえば、オブジェクトリテラルを扱う際に、そのプロパティが厳密にチェックされない場合、型が一般化されてしまいます。
const user = { name: "Alice", age: 25 };
const newUser = { ...user, address: "123 Street" }; // addressプロパティが追加される
このようにして作成されたnewUser
は、型が広がってしまう可能性があり、意図しない型の使用を許してしまいます。この場合、スプレッド演算子などを使って型を拡張する際には、型を明示的に定義することが有効です。
type User = { name: string; age: number; };
type NewUser = User & { address: string; };
const newUser: NewUser = { ...user, address: "123 Street" }; // 型が明確になる
回避策のまとめ
型推論の落とし穴を回避するためには、次のようなベストプラクティスを守ることが重要です。
- 暗黙的な
any
を避け、型を明示的に宣言する。 - 動的なプロパティアクセスには
keyof
を活用し、型の誤推論を防ぐ。 - 予期しない型の広がりを防ぐため、型注釈や型定義を明確にする。
これらの対策を行うことで、TypeScriptの強力な型システムを最大限に活用し、型推論によるエラーを未然に防ぐことができます。
応用編:複雑な型推論における実例
TypeScriptでは、より複雑なデータ構造やユースケースに対しても型推論を適用することができます。特に、大規模なプロジェクトや多層的なデータモデルを扱う際には、keyof
やtypeof
を組み合わせた型推論が有効です。このセクションでは、これらの応用例を通して、複雑な型推論の実用的な方法を紹介します。
ネストされたオブジェクトの型推論
ネストされたオブジェクトや配列を扱う場合、keyof
を使ってプロパティの型を取得することで、型安全な操作を行うことができます。以下の例では、ネストされたオブジェクトのプロパティに安全にアクセスできる型推論を実装します。
type User = {
id: number;
personalInfo: {
name: string;
address: {
street: string;
city: string;
};
};
};
const user: User = {
id: 1,
personalInfo: {
name: "Alice",
address: {
street: "123 Main St",
city: "Wonderland",
},
},
};
function getNestedProperty<T, K1 extends keyof T, K2 extends keyof T[K1]>(
obj: T,
key1: K1,
key2: K2
): T[K1][K2] {
return obj[key1][key2];
}
const city = getNestedProperty(user, "personalInfo", "address"); // 型安全にアクセス
console.log(city.city); // "Wonderland"
この例では、getNestedProperty
関数を使用して、オブジェクトuser
のネストされたプロパティaddress
に型安全にアクセスしています。keyof
を利用して各プロパティの型を動的に取得し、型チェックを強化しています。
ジェネリック型を用いた配列操作
TypeScriptのジェネリック型を使えば、配列やタプルのようなデータ構造にも柔軟な型推論を適用できます。次の例では、ジェネリック型とkeyof
を組み合わせて、配列の要素に対する型安全な操作を実現しています。
function getArrayElement<T>(arr: T[], index: number): T {
return arr[index];
}
const numbers = [10, 20, 30];
const names = ["Alice", "Bob", "Charlie"];
const num = getArrayElement(numbers, 1); // 型推論により number と推論
const name = getArrayElement(names, 2); // 型推論により string と推論
console.log(num); // 20
console.log(name); // Charlie
この例では、getArrayElement
関数がジェネリック型を使用して、配列の要素に型安全にアクセスできるようになっています。これにより、配列のデータ型が異なっていても、同じ関数で処理できる汎用性が保たれつつ、型チェックも強化されます。
条件付き型と`keyof`を用いた複雑な型推論
条件付き型を使うことで、動的な型推論をさらに強化できます。以下の例では、オブジェクトのプロパティに基づいて返り値の型を切り替える関数を実装しています。
type Product = {
id: number;
name: string;
inStock: boolean;
};
type ProductKeys = keyof Product;
function getProductProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const product: Product = { id: 101, name: "Laptop", inStock: true };
const productId = getProductProperty(product, "id"); // number型が推論される
const productName = getProductProperty(product, "name"); // string型が推論される
const productStock = getProductProperty(product, "inStock"); // boolean型が推論される
console.log(productId); // 101
console.log(productName); // "Laptop"
console.log(productStock); // true
この例では、keyof
とジェネリック型を組み合わせることで、オブジェクトのプロパティに応じた型推論を実現しています。これにより、動的にプロパティにアクセスしても型安全性が損なわれることはありません。
複数のユースケースに対応する型推論
次に、複数のユースケースに対応できる柔軟な型推論の例を見ていきます。ユニオン型や条件付き型を使用して、異なるパターンに基づく動的な型推論を実現する方法です。
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; sideLength: number };
function getArea(shape: Shape): number {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
} else {
return shape.sideLength ** 2;
}
}
const circle: Shape = { kind: "circle", radius: 5 };
const square: Shape = { kind: "square", sideLength: 10 };
console.log(getArea(circle)); // 78.5398...
console.log(getArea(square)); // 100
この例では、Shape
型に対してkind
プロパティの値に応じた処理を行うことで、動的な型推論を実現しています。ユニオン型と条件付き型を組み合わせることで、複数のユースケースに対応する型安全なロジックを実装することが可能です。
このように、keyof
やtypeof
を利用して複雑な型推論を行うことで、柔軟かつ型安全なコードを記述でき、複雑なデータ構造やユースケースにも対応できるようになります。
TypeScriptで型推論を活用するベストプラクティス
TypeScriptの型推論は、コードの安全性を確保しながら、より効率的でメンテナンスしやすいプログラムを作成するための強力なツールです。しかし、適切な方法で使用しないと、逆にコードが複雑化したり、型エラーが発生する可能性があります。このセクションでは、TypeScriptで型推論を活用するためのベストプラクティスを紹介します。
初期値を活用した明示的な型推論
TypeScriptの型推論は、変数の初期値に基づいて自動的に型を推測します。そのため、可能な限り初期値を設定することで、明示的に型を宣言しなくても型安全なコードを作成できます。
let userName = "Alice"; // userName は string 型に推論される
let userAge = 30; // userAge は number 型に推論される
初期値が明確な場合、TypeScriptが自動的に型を推論してくれるため、コードの記述量を削減でき、可読性も向上します。初期値がない場合は、any
型になる可能性があるため、意識的に型を指定するか、初期値を設定することが重要です。
ジェネリック型の活用
ジェネリック型を使うことで、柔軟かつ再利用性の高いコードを実現できます。特に、関数やクラスにおいて、異なる型に対して共通の処理を行う場合に有効です。
function identity<T>(value: T): T {
return value;
}
const stringValue = identity("Hello"); // string 型として推論
const numberValue = identity(42); // number 型として推論
ジェネリック型を使用すると、異なる型に対しても型安全に処理を行うことができ、より汎用性の高いコードが書けます。ジェネリック型を使うときは、明示的に型を指定しなくても、TypeScriptが自動で適切な型を推論してくれるため、シンプルかつ安全です。
適切な型注釈を加える
型推論が強力な場合でも、意図的に型注釈を加えることで、コードの意図を明確にし、誤った型推論を防ぐことができます。特に、関数の引数や複雑な型の場合は、明示的に型を宣言することが推奨されます。
function sum(a: number, b: number): number {
return a + b;
}
このように、引数や返り値の型を明示することで、関数の使い方が明確になり、間違った引数を渡したり、予期しない結果を得るリスクを減らせます。
`keyof`や`typeof`を使った型安全なアクセス
keyof
やtypeof
を活用して、オブジェクトや変数の型に基づいて動的にアクセスすることで、型安全なコードを書くことができます。これにより、動的にプロパティや変数にアクセスする際も、型チェックが行われ、バグを防ぐことが可能です。
const user = { name: "Alice", age: 25 };
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const name = getProperty(user, "name"); // "name"の型は string と推論
このように、keyof
やtypeof
を使用してオブジェクトのプロパティに型安全にアクセスすることで、型の不整合を未然に防ぐことができます。
ユニオン型や条件付き型を適切に利用する
ユニオン型や条件付き型を使うと、異なる型に対して共通の処理を記述し、コードの柔軟性を高めることができます。また、型推論を基に動的に型を切り替えることができ、複雑な型を扱う際に非常に便利です。
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; sideLength: number };
function getArea(shape: Shape): number {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
} else {
return shape.sideLength ** 2;
}
}
このように、異なる型を統一的に扱うユニオン型と条件付き型を活用することで、型推論の強みを最大限に引き出しつつ、型安全なコードを書くことができます。
型推論の限界を理解する
型推論には限界があるため、適切に型を明示することも重要です。TypeScriptの型推論が誤って働く場合、予期せぬエラーやバグが発生することがあります。特に、複雑なデータ構造や関数型を扱う場合は、型注釈を追加して型推論を補完することで、コードの安全性を向上させることができます。
型推論と型注釈のバランス
最も効率的なTypeScriptの使い方は、型推論に任せつつ、必要な場所にのみ型注釈を加えることです。冗長な型注釈はコードを複雑にし、可読性を下げることがありますが、適切な場所で型を明示することは、バグの防止と保守性向上につながります。
このバランスを保ちながら、型推論の強力な機能を活用することで、効率的かつ安全なTypeScriptのコードを書けるようになります。
練習問題:`keyof`と`typeof`を使った型推論
これまで学んだkeyof
やtypeof
を使った型推論の知識を実際に手を動かして確認できる練習問題をいくつか紹介します。この演習を通して、TypeScriptで型推論を活用した型安全なコードの書き方をより深く理解しましょう。
問題1: オブジェクトのプロパティを型安全に取得する関数
以下のコードでは、オブジェクトperson
のプロパティに型安全にアクセスできるような関数getProperty
を実装してください。keyof
を活用し、指定したキーに対して対応する型の値を取得できるようにします。
type Person = {
name: string;
age: number;
address: string;
};
const person: Person = {
name: "John",
age: 25,
address: "123 Main St",
};
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
// 関数を実装してください
}
const name = getProperty(person, "name"); // "John" が取得されるべき
const age = getProperty(person, "age"); // 25 が取得されるべき
問題2: オブジェクトのプロパティを動的に更新する
次に、setProperty
という関数を実装して、指定したキーに対応するプロパティの値を型安全に更新できるようにしてください。この問題では、keyof
を使用して、プロパティの更新時に誤った型を渡さないようにする必要があります。
type Product = {
id: number;
name: string;
price: number;
};
const product: Product = {
id: 1,
name: "Laptop",
price: 1000,
};
function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
// 関数を実装してください
}
setProperty(product, "name", "Smartphone"); // OK
setProperty(product, "price", 1200); // OK
// setProperty(product, "id", "123"); // エラーが出るべき(型が不一致)
問題3: `typeof`を使った動的な型取得
次のコードでは、typeof
を使って変数の型を動的に取得し、その型を他の変数に適用してください。変数a
とb
の型はtypeof
を使って推論されるべきです。
const a = 42;
const b = "Hello";
let x: typeof a; // aの型を取得してください
let y: typeof b; // bの型を取得してください
x = 100; // OK
// x = "100"; // エラーが出るべき
y = "World"; // OK
// y = 123; // エラーが出るべき
問題4: ジェネリック型を使った配列の型推論
ジェネリック型とtypeof
を組み合わせて、配列の要素を型安全に取得する関数getArrayElement
を実装してください。この関数は、配列の要素に型安全にアクセスし、要素の型に基づいて型推論が行われるべきです。
function getArrayElement<T>(arr: T[], index: number): T {
// 関数を実装してください
}
const numbers = [10, 20, 30];
const strings = ["Alice", "Bob", "Charlie"];
const num = getArrayElement(numbers, 1); // number型が推論される
const str = getArrayElement(strings, 2); // string型が推論される
console.log(num); // 20
console.log(str); // Charlie
問題5: 条件付き型を使った型推論
条件付き型を使用して、特定の条件に基づいて異なる型を返す関数を実装してください。以下のコードでは、引数が文字列の場合はそのまま文字列を返し、数値の場合は文字列に変換して返すようにしてください。
function processInput<T>(input: T): T extends string ? string : number {
// 関数を実装してください
}
const result1 = processInput("Hello"); // "Hello" (string型が推論される)
const result2 = processInput(12345); // "12345" (number型が推論される)
これらの練習問題に取り組むことで、keyof
やtypeof
を活用した型推論の応用的な使い方を体得できるでしょう。
まとめ
本記事では、TypeScriptにおけるkeyof
やtypeof
を活用した型推論の基礎から応用までを詳しく解説しました。これらの演算子を使うことで、型安全なコードを書き、柔軟に型情報を扱えるようになります。また、型推論の落とし穴やそれを回避するためのベストプラクティスも学び、実際に練習問題を通じて知識を深めることができました。TypeScriptの強力な型システムを最大限に活用するために、これらの知識を日常の開発でぜひ活用してください。
コメント