TypeScript: 計算プロパティ名を使わずにコードをスマートに書く方法
TypeScriptにおける「計算プロパティ名」とエラーメッセージ解説
TypeScriptでオブジェクトリテラルやインターフェースを定義する際、プロパティ名に式を使用できる機能があります。これは「計算プロパティ名」と呼ばれ、柔軟な型定義を可能にする便利な機能です。
しかし、計算プロパティ名を使用する際には、いくつかの制約があります。その中でも、よくあるエラーメッセージが「TypeScript A computed property name in a type literal must directly refer to a built-in symbol」です。
このエラーメッセージは、計算プロパティ名がリテラル型または組み込みシンボルを参照していないことを意味します。つまり、計算プロパティ名が変数や関数などの式を参照している場合に発生します。
詳細解説
- リテラル型とは?
リテラル型は、具体的な値そのものを型として表現するものです。例えば、文字列リテラル "Hello" や数値リテラル 10 はリテラル型です。
- 組み込みシンボルとは?
組み込みシンボルは、TypeScriptが提供する特殊な型です。例えば、Symbol.iterator
や Symbol.hasOwn
などがあります。
エラー回避方法
このエラーを回避するには、計算プロパティ名がリテラル型または組み込みシンボルを参照するようにする必要があります。
具体的には、以下の方法があります。
- リテラル型を使用する
type User = {
[name: string]: string; // 文字列リテラル型を使用
};
- 組み込みシンボルを使用する
type User = {
[Symbol.iterator]: () => Iterator<string>; // 組み込みシンボル Symbol.iterator を使用する
};
- 型ガードを使用する
計算プロパティ名がリテラル型または組み込みシンボルであることを保証できない場合は、型ガードを使用して型を絞り込むことができます。
type User = {
[key: string]: string; // 型ガードを使用
};
function isLiteralKey(key: string): key is string {
return typeof key === 'string' && key.length > 0;
}
function getUserData(user: User, key: string): string | undefined {
if (isLiteralKey(key)) {
return user[key];
} else {
return undefined;
}
}
- 型ガードを使用して、計算プロパティ名の型をより詳細に制御することもできます。
- 計算プロパティ名は、リテラル型または組み込みシンボルを参照する必要があります。
例1:文字列リテラル型を使用した計算プロパティ名
type User = {
[name: string]: string; // 文字列リテラル型を使用
};
const user: User = {
"name": "John Doe",
"age": 30,
"email": "[email protected]"
};
console.log(user.name); // "John Doe" を出力
console.log(user.age); // 30 を出力
console.log(user.email); // "[email protected]" を出力
この例では、User
型は、文字列リテラル型のプロパティを持つオブジェクトを表します。つまり、user
オブジェクトのプロパティ名はすべて文字列であり、それぞれのプロパティの値は文字列である必要があります。
例2:組み込みシンボルを使用した計算プロパティ名
type User = {
[Symbol.iterator]: () => Iterator<string>; // 組み込みシンボル Symbol.iterator を使用する
};
const user: User = {
[Symbol.iterator]() {
let i = 0;
return {
next() {
if (i < user.age) {
return { value: user.name + (i + 1), done: false };
} else {
return { value: undefined, done: true };
}
i++;
}
};
}
};
for (const item of user) {
console.log(item); // "John Doe1", "John Doe2", "John Doe3" を出力
}
この例では、User
型は、組み込みシンボル Symbol.iterator
を持つオブジェクトを表します。つまり、user
オブジェクトはイテレータとして扱われ、for...of
ループなどでループ処理することができます。
例3:型ガードを使用した計算プロパティ名
type User = {
[key: string]: string; // 型ガードを使用
};
function isLiteralKey(key: string): key is string {
return typeof key === 'string' && key.length > 0;
}
function getUserData(user: User, key: string): string | undefined {
if (isLiteralKey(key)) {
return user[key];
} else {
return undefined;
}
}
const user: User = {
"name": "John Doe",
age: 30,
"email": "[email protected]"
};
console.log(getUserData(user, "name")); // "John Doe" を出力
console.log(getUserData(user, "age")); // undefined を出力
console.log(getUserData(user, "email")); // "[email protected]" を出力
TypeScriptにおける「計算プロパティ名」の代替方法
しかし、計算プロパティ名の使用にはいくつかの制約があり、状況によっては他の方法の方が適切な場合もあります。
代替方法
関数を使用する
計算プロパティ名の代わりに、関数を使用して動的なプロパティ名にアクセスすることができます。
type User = {
getName(): string;
getAge(): number;
getEmail(): string;
};
const user: User = {
getName: () => "John Doe",
getAge: () => 30,
getEmail: () => "[email protected]"
};
console.log(user.getName()); // "John Doe" を出力
console.log(user.getAge()); // 30 を出力
console.log(user.getEmail()); // "[email protected]" を出力
この例では、User
型は、getName
、getAge
、getEmail
のようなメソッドを持つオブジェクトを表します。これらのメソッドは、動的なプロパティ名にアクセスするために使用することができます。
型パラメータを使用する
ジェネリック型を使用する場合は、型パラメータを使用して動的なプロパティ名にアクセスすることができます。
type User<K extends string> = {
[key in K]: string;
};
const user: User<'name' | 'age' | 'email'> = {
name: "John Doe",
age: 30,
email: "[email protected]"
};
console.log(user.name); // "John Doe" を出力
console.log(user.age); // 30 を出力
console.log(user.email); // "[email protected]" を出力
この例では、User
型は、ジェネリック型パラメータ K
を持つオブジェクトを表します。K
は、オブジェクトのプロパティ名の型を表す型パラメータです。
型アサーションを使用する
type User = {
[key: string]: string; // 型アサーションを使用
};
const user: User = {
name: "John Doe",
age: 30,
email: "[email protected]"
};
const userName: string = user['name']; // 型アサーションを使用
console.log(userName); // "John Doe" を出力
この例では、userName
変数に型アサーションを使用して string
型を指定しています。これにより、user['name']
の値が string
型であることが保証されます。
typescript