【TypeScript】Index Signature インデックスシグネチャ


- オブジェクトのメンバーに、文字列や数値でアクセスできるようになります。
- 参照するプロパティを動的に変える場合などに使います。

## 使用例
```ts
// 元素
interface IChemistryElement {
  readonly symbol: string
  readonly atomicWeight: number
}
// 周期表(=元素の一覧)
const PeriodicTable = new class {

  // インデックスシグネチャ
  readonly [key: string]: IChemistryElement | undefined

  public readonly hydrogen: IChemistryElement = { symbol: 'H', atomicWeight: 1.008 }
  public readonly helium: IChemistryElement = { symbol: 'He', atomicWeight: 4.003 }
}

// 普通はこのようにしか参照できないところ、
console.log(PeriodicTable.hydrogen.atomicWeight) // '1.008'
// インデックスシグネチャを使うと文字列で参照できるようになります。
console.log(PeriodicTable['hydrogen'].atomicWeight) // '1.008'
```

例えば以下のような書き方が可能になります。

```ts
/*
  <button onclick="showSymbol('hydrogen')">水素の元素記号を表示します</button>
  <button onclick="showSymbol('helium')">ヘリウムの元素記号を表示します</button>
*/
function showSymbol (elementName: string) {
  alert(`元素記号は ${PeriodicTable[elementName].symbol} です。`)
}
```

## メンバーの動的な追加

存在しないメンバーに代入してもエラーとはならず、自動的にプロパティが生成されます。
これを避けるには、インデックスシグネチャの定義に`readonly`を指定します。動的な追加ができなくなります。

```ts:readonlyなし
const PeriodicTable = new class {
  [key: string]: IChemistryElement | undefined
}
// OK
PeriodicTable.lithium = { symbol: 'Li', atomicWeight: 6.941 }
PeriodicTable['beryllium'] = { symbol: 'Be', atomicWeight: 9.012 }
```
```ts:readonlyあり
const PeriodicTable = new class {
  readonly [key: string]: IChemistryElement | undefined
}
// コンパイルエラー
PeriodicTable.lithium = { symbol: 'Li', atomicWeight: 6.941 }
PeriodicTable['beryllium'] = { symbol: 'Be', atomicWeight: 9.012 }
```

## 主な注意点

### キーの型の制約
シグネチャのキーにできるのは`string`か`number`のみです。

### メンバーの型の制約

そのクラスにはシグネチャで指定した型のメンバーしか定義することができなくなります。

```ts
const PeriodicTable = new class {
　[key: string]: IChemistryElement

  // エラー
  // 型 'boolean' のプロパティ 'aaa' を文字列インデックス型 'IChemistryElement' に割り当てることはできません。
  aaa = true
}
```

### 存在しないプロパティ

存在しないメンバーを指定することも可能です。

```ts
console.log(PeriodicTable['spacium']) // undefined
```

ただしシグネチャで定義した通りの型で認識されてしまうため、型の安全性を破壊する可能性があります。

```ts
// 存在しないメンバー"spacium"のプロパティを参照しようとして実行時エラーになる
console.log(PeriodicTable.spacium.atomicWeight)
```

コンパイラがundefinedの可能性を検知できるようにするには、シグネチャの型に` | undefined`を加える必要があります。

```ts:危ない
const PeriodicTable = new class {
　[key: string]: IChemistryElement
  public readonly hydrogen: IChemistryElement = { symbol: 'H', atomicWeight: 1.008 }
}
const h = PeriodicTable.hydrogen // IChemistryElement
const s = PeriodicTable.spacium // IChemistryElement <- !?
```

```ts:安全
const PeriodicTable = new class {
  [key: string]: IChemistryElement | undefined // undefined を追加
  public readonly hydrogen: IChemistryElement = { symbol: 'H', atomicWeight: 1.008 }
}
const h = PeriodicTable.hydrogen // IChemistryElement
const s = PeriodicTable.spacium // IChemistryElement | undefined <- OK!
```
## 所感
- 区分オブジェクトの表現に適していそうと感じました
- 例えばVueでデータテーブルのコンポーネントを作ろうと思ったら、行をジェネリッククラスにしてそのプロパティを動的に参照すると綺麗になりそうです

```vue
<template>
  <table>
    <tr v-for="row in data" :key="row">
      <td v-for="key in dataKeys" :key="key">
        {{ row[key] }}
      </td>
    </tr>
  </table>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'

@Component({})
export default class GridView<T extends { [key: string]: any }> extends Vue {
  @Prop() public data!: T[]
}
</script>
```

## 参考
- Index Signatures - TypeScript Deep Dive 日本語版
  - https://typescript-jp.gitbook.io/deep-dive/type-system/index-signatures
- David-Else/modern-typescript-with-examples-cheat-sheet
  - https://github.com/David-Else/modern-typescript-with-examples-cheat-sheet