【TypeScript】インデックスシグネチャ(Index Signatures)の概要と利用方法

JavaScript

インデックスシグネチャ(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型のfirstNamelastNameプロパティを持つオブジェクト』の型を表現できます。具体例は以下の通りです。

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(マップ型)の概要と利用方法まとめで紹介しています。

参考

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!