目次
インデックスシグネチャ(Index Signatures、インデックス型)について
インデックスシグネチャ(Index Signatures、インデックス型)とは、インデックス(添字)を利用してオブジェクトのプロパティ(キー)の型を定義する機能のことをいいます。
インデックスシグネチャは[key: T]: U
のように記述します。
[key: T]: U
は『オブジェクトのプロパティ(key
)は型T、プロパティの値は型U』を表現します。
インデックスシグネチャのメリット・使いどころ
インデックスシグネチャの型定義は任意のプロパティに適用されるため、一つひとつのプロパティに型定義をする手間が省けます。また、様々なプロパティがセットされるオブジェクトの型を表現できます。
例えば、『プロパティはString型、値はString型』をインデックスシグネチャで表現すると以下のようになります。インデックスシグネチャの記述方法は後述します。
type ObjType = {
// 『プロパティはString型、値はString型』を表現
[key: string]: string;
};
// 『プロパティはString型、値はString型』を満たせば
// 任意のプロパティを設定できる
const userObj: ObjType = {
firstName: "Taro",
lastName: "Suzuki",
};
const bookObj: ObjType = {
title: "I am a Cat",
};
インデックスシグネチャの定義方法
インデックスシグネチャの基本形は[key名: プロパティの型]: 『valueの型』
です。key名は任意の名前で問題ありません。
TypeScript 4.4.2現在、インデックスシグネチャで利用できるプロパティの型は以下の4つです。
- String型
- Number型
- Symbol型
- Template Literal Types(テンプレートリテラル型、テンプレート文字列型)
以下ではそれぞれの場合の具体例を紹介します。
プロパティをString型にする場合
プロパティをString型、値をNumber型にする例は以下の通りです。
type ObjType = {
[key: string]: number;
};
const obj: ObjType = {
hello: 10,
};
console.log(obj["hello"]);
// 10
プロパティや値の型が違うとエラーになります。
type ObjType = {
[key: string]: number;
};
// NG: 値の型が違う
const invalidValueObj: ObjType = {
hello: "Hello",
}; // Type 'string' is not assignable to type 'number'.ts(2322)
Number型のプロパティはString型に変換される
プロパティや値の型が違うとエラーになります。
しかし、Number型のプロパティはString型に変換されるため、インデックスシグネチャでプロパティをString型に指定していてもNumber型のプロパティが設定できます。
type ObjType = {
[key: string]: number;
};
/// OK
// Number型のプロパティを設定できる
const obj: ObjType = {
1: 10,
};
// オブジェクトのプロパティ一覧(key一覧)を取得
const keys = Object.keys(obj);
// ['1']
// プロパティ『1』はString型に変換されている
console.log(typeof keys[0]);
// string
// String型でもNumber型でも参照可能
console.log(obj['1']);
// 10
console.log(obj[1]);
// 10
String型のプロパティの場合、値はドッドでも参照可能
String型のプロパティの値はドットでも参照できます。
type ObjType = {
[key: string]: number;
};
const obj: ObjType = {
hello: 10,
};
console.log(obj["hello"]);
// 10
console.log(obj.hello);
// 10
プロパティをNumber型にする場合
プロパティをNumber型、値をString型にする例は以下の通りです。
type ObjType = {
[key: number]: string;
};
const obj: ObjType = {
0: "Hello",
};
console.log(obj[0]);
// Hello
// 数値を文字列リテラルで表現する形でもOK
const obj2: ObjType = {
"0": "Hello",
};
console.log(obj2[0]);
// Hello
プロパティや値の型が違うとエラーになります。
type ObjType = {
[key: number]: string;
};
// NG: プロパティの型が違う
const invalidPropertyObj: ObjType = {
hello: "Hello",
};
// Type '{ hello: string; }' is not assignable to type 'ObjType'.
// Object literal may only specify known properties, and 'hello' does not exist in type 'ObjType'.ts(2322)
// NG: 値の型が違う
const invalidValueObj: ObjType = {
0: 10,
}; // Type 'number' is not assignable to type 'string'.ts(2322)
プロパティをSymbol型にする場合
TypeScript 4.4からはSymbol型もインデックスシグネチャに利用できます。1
プロパティをSymbol型、値をString型にする例は以下の通りです。
type ObjType = {
[key: symbol]: string;
};
const sym = Symbol();
const obj: ObjType = {
[sym]: "value",
};
console.log(obj[sym]);
// value
プロパティをTemplate Literal Types(テンプレートリテラル型、テンプレート文字列型)にする場合
Template Literal Types(テンプレートリテラル型、テンプレート文字列型)はTypeScript 4.1から導入された型で2、JavaScriptのテンプレートリテラルを利用して型を定義します。
TypeScript 4.4からはTemplate Literal Typesもインデックスシグネチャに利用できます。1
インデックスシグネチャの基本形は[key名: プロパティの型]: 『valueの型』
ですが、Template Literal Typesの場合はin
を利用して[key名 in テンプレートリテラル]: 『valueの型』
と記述します。
プロパティの型をTemplate Literal Types、値をString型にする例は以下の通りです。
type ObjType = { [key in `hello${string}`]: string };
// 以下のように型推論される
// type ObjType = {
// [x: `hello${string}`]: string;
// }
const obj: ObjType = {
helloJp: "こんにちは",
helloEn: "Hello",
helloCn: "你好",
};
console.log(obj.helloJp);
// こんにちは
console.log(obj["helloJp"]);
// こんにちは
プロパティに複数の型を許容する場合
『値はString型、プロパティはNmber型もしくはString型』を表現するインデックスシグネチャは以下の通りです。
type ObjType = {
[key: number]: string;
[key: string]: string;
};
const obj: ObjType = {
0: "Hello",
hello: "Hello",
};
console.log(obj[0]);
// Hello
console.log(obj["0"]);
// Hello
console.log(obj["hello"]);
// Hello
console.log(obj.hello);
// Hello
以下のようにin
を利用した表現もできます。
type ObjType = { [key in number | string]: string };
// 以下のように型推論される
// type ObjType = {
// [x: string]: string;
// [x: number]: string;
// };
const obj: ObjType = {
0: "Hello",
hello: "Hello",
};
インデックスシグネチャを適用させるプロパティを制限したい場合
Mapped Typesを利用すると、インデックスシグネチャを適用させるプロパティが制限できます。
インデックスシグネチャとMapped Typesを組み合わせることで、例えば『String型のfirstName
とlastName
プロパティを持つオブジェクト』の型を表現できます。具体例は以下の通りです。
type userAttributes = "firstName" | "lastName";
type userObjType = {
[key in userAttributes]: string;
};
// 以下のように型推論される
// type userObjType = {
// firstName: string;
// lastName: string;
// }
// OK
const taro: userObjType = {
firstName: "Taro",
lastName: "Suzuki",
};
// NG
// 対象外のプロパティが利用されている
const jiro: userObjType = {
firstName: "Taro",
lastName: "Suzuki",
phone: "090-1234-5678",
};
// assignable to type 'userObjType'.
// Object literal may only specify known properties, and 'phone' does not exist in type 'userObjType'.
Mapped Typesの詳細解説はMapped Types(マップ型)の概要と利用方法まとめで紹介しています。
参考
- TypeScript Deep Dive 日本語版『Index signature(インデックス型)』
- TypeScript:インデックスシグネチャが型安全を破壊する例
- インデックスシグネチャ
- TypeScript 4.4 変更点 まとめ
さいごに
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!