TypeScriptのインデックス型とインデックスアクセス型：名前が似ているけど？

## インデックス型 と インデックスアクセス型

* **インデックス型(Index Signature)**
→ オブジェクトが動的なキー（事前に決まっていないキー）を持つ場合に、そのキーに対する値の型を定義する方法
* **インデックスアクセス型（indexed access types）** 
→ オブジェクトの「キー」を使って、そのキーに対応する値の型を取得する方法

## インデックス型 (Index Signature)

### :point_right: オブジェクトのキーが事前に決まっていない！？

オブジェクトが **「動的なキー」（事前に決まっていないキー）** を持つ場合に使用される。
```typescript:例）インデックス型
type ObjType = {
  [K: string]: number; // 動的なキーを持ち、その値がnumber型
};
```
:::note info
**インデックス型の特徴**
- フィールド名の表現部分が **[K: string]**
- このKの部分はキーの型変数名（任意）で、Kやkeyにするのが一般的
- **キーの型** は、<font color="red">`string`、`number`、`symbol`</font>のみ指定できる

:::

```typescript:例）インデックス型
type ObjType = {
  [K: string]: number;
};

const obj: ObjType = {
  foo: 1, // foo が動的なキーになる
  bar: 2  // bar が動的なキーになる
};

console.log(obj['foo']); // 1　（ブラケット記法）
console.log(obj.bar); // 2　（ドット記法）
```

**type** や **interface** は、**複数回使いたい型を定義、型の再利用、拡張を行いたい場合**に使う。
```typescript:例）typeを使って型を再利用
type ObjType = { // 複数回使いたい型を定義
  [K: string]: number; 
};

let obj1: ObjType = { a: 1 };
let obj2: ObjType = { b: 2 };
```

```typescript:例）typeで交差型（&）を使って型を拡張
type ObjType = {
  [K: string]: number;
};

type ExtendedType = ObjType & { 
  anotherKey: string; 
};

const obj: ExtendedType = { 
  a: 1, 
  anotherKey: 'value' 
};
```

**一度きりで使う型の定義**には、わざわざ type や interface を使う必要はない。
```typescript:例）インラインで型指定
// type や interface でわざわざ定義しない
let obj: {
  [K: string]: number; // 直接指定する
} = { a: 1 }; 
```

:::note info
**インラインで型を指定** <br> 1 度きりで使う型定義の場合、type や interface で 型定義せず、必要な型をその場で直接指定する
:::

### :point_right: 柔軟に型を指定する！
インデックス型は、すべてのプロパティが同じ型であることが求められる。

```typescript
type ObjType= {
  [K: string]: number; // すべてのプロパティがnumber型でなければならない
};

const obj: ObjType = {
  a: 1, // number 型
  b: 2, // number 型
  c: 'hello' // エラー: string 型は許容されていない
};
```
[K: string]: number により、オブジェクトのプロパティが number 型に制限されている。
複数の型を許容したい場合、**ユニオン型** で型を柔軟に指定する。

```typescript:例）ユニオン型で型を柔軟に指定
type ObjType = {
  [K: string]: string | number; // ユニオン型
};

const obj: ObjType = {
  a: 1,
  b: 2,
  c: 'hello'
};
```
### :point_right: undefinedに気をつけて！！

:::note warn
オブジェクトに動的にキーを追加できる一方、存在しないキーにアクセスしてもコンパイルエラーにはならず、undefinedが返される。
:::
```typescript:例）インデックス型の問題点
type UserType = {
  [K: string]: string;
};

const user: UserType = {
  name: "Taro",
  country: "Japan",
};

console.log(user['name']); // Taro
console.log(user['unknownKey']); //　存在しないキーだと undefined が返される
```
* **存在しないキー**を使用しているが**コンパイルエラーにはならない**
* undefined を予期せず扱ってしまうとエラーを引き起こす可能性がある

#### undefined対策 :arrow_right: 例1. undefinedを許容し、デフォルト値を設定しておく

```typescript:例）undefinedを許容し、デフォルト値を設定
type UserType = {
  [K: string]: string | undefined; // undefinedを許容
};

const user: UserType = {
  name: "Taro",
  country: "Japan",
};

// キーが存在しない場合、`undefined` が返されるため値を設定する
const hobby = user['unknownKey'] ?? 'default';
console.log(hobby); // 'default'
```
`?? 演算子(Null合体演算子)`を使用して、null または undefined の場合にデフォルト値を設定し安全に扱う。

#### undefined対策 :arrow_right:  例2. TSConfigでnoUncheckedIndexedAccessを設定する
:::note info
**noUncheckedIndexedAccess**
インデックス型のプロパティや配列要素を参照したときundefinedのチェックを必須にするコンパイラオプション。
```typescript
// TSConfig
{
  "compilerOptions": {
    "noUncheckedIndexedAccess": true
  }
}
```
:::

設定を有効にした場合、[K: string]: string と定義された型は、[K: string]: string | undefined として扱われるようになる。

```typescript:例）noUncheckedIndexedAccessを設定した場合
type UserType = {
  [K: string]: string; // [K: string]: string | undefined; と同じ扱いになる
};

const user: UserType = {
  name: "Taro",
  country: "Japan",
};

const hobby = user['unknownKey'] ?? 'default';
console.log(hobby); // 'default'
```

### :point_right: インデックス型 と Mapped Types

:::note info
**Mapped Typesの特徴**
type MappedType = { 
　[K in keyof T]: NewType; 
}　
* `K:` キー
* `T:` 元となる型
例）オブジェクト形式 { a: number; b: string } 
* `NewType`: 新しく設定する型
例）boolean の場合、MappedType のすべてのプロパティの型は boolean

型のすべてのプロパティに対して型の変換や操作が可能になる。
:::

**:ballot_box_with_check: インデックス型**：
**任意のキーを設定できる**ため、指定したキーがオブジェクトに存在しない場合、**undefinedが返される可能性**があり、型の安全が損なわれる場合がある。
```typescript:例）インデックス型
type IndexType = {
  [K: string]: number;  // 任意のキーを許容
};
```
**:ballot_box_with_check: Mapped Types**：
インデックス型とは異なり、**型が決まっている場合**に、型を明示的に制限し、**許可されているキーのみを使用できる**ようにする。事前に定義されたプロパティ名に基づいて、**新しい型を生成する際に使用**。型の変換やプロパティ名の追加・削除などに役立つ。


```typescript:例）Mapped Types
//　事前に型が決まっている
type Person = {
  name: string;
  age: number;
};
// name と age のプロパティに対して 新しい型 boolean を生成し適用させる 
type PersonFlags = {
  [K in keyof Person]: boolean; 
};
```
Mapped Types を使用する際に、keyof を使わずに、キーのユニオン型を直接使うことも可能
```typescript:例）keyof を使わずに、キーのユニオン型を直接使う
type SupportLang = "en" | "fr" | "it"; // キーがあらかじめ決まっている
type Butterfly = {
  [K in SupportLang]: string;  // "en", "fr", "it" のキーしか使えない
}

const butterflies: Butterfly = {
  en: "Butterfly",
  fr: "Papillon",
  it: "Farfalla",
  de: "Schmetterling",  // エラー：'de'は'Butterfly'型には含まれない
};
```
Mapped Types を使用する際に、型を新たに設定する場合
```typescript:例）Mapped Typesで型を新たに設定
type SupportLang = "en" | "fr" | "it"; 
type Butterfly = {
  [K in SupportLang]: string;  // "en", "fr", "it" のキーしか使えない
}

// Mapped Typesを使って、Butterfly 型のすべてのプロパティをstringからnumberに変換
type ButterflyNumbered = {
  [K in keyof Butterfly]: number;
};
```
Mapped Types によって新しく生成された型 ButterflyNumbered は number 型に変換

```
type ButterflyNumbered = {
  en: number;
  fr: number;
  it: number;
};
```
すべてのプロパティをオプショナル(任意)にしたり、readonly にしたりすることもできる。
```typescript:例）Mapped Types で オプショナル や readonly に
type Butterfly = {
  en: string;
  fr: string;
  it: string;
};

type ButterflyOptional = {
  [K in keyof Butterfly]?: string;  // すべてのプロパティをオプショナルに
};

type ButterflyReadonly = {
  readonly [K in keyof Butterfly]: string;  // すべてのプロパティをreadonlyに
};
```

|  | インデックス型 | Mapped Types　|
|:-----------|:------------|:------------|
| 目的 | 動的なプロパティ名に型を適用     | 特定の型のプロパティに基づいて新しい型を生成 |
| プロパティ名（キー） | 事前に決まっていない（動的に決まる）| 事前に定義された型のプロパティ名（keyof T）に基づいて決まる|
| 型安全 | キーが動的なので、undefinedチェックが必要な場合がある | 事前に定義されたプロパティ名を使うため、許可されたキーのみ使用されるので型安全 |


## インデックスアクセス型（ indexed access types ）
### :point_right: アクセスして型を取ってくる！？
```typescript:例）インデックスアクセス型
type A = { 
    foo: number 
};

type Foo = A["foo"]; // number型
```
:::note info
**インデックスアクセス型**
→ **型のプロパティ**にアクセス
→ **<font color="red">ブラケット記法でアクセス</color>** して**その型を取得**
:::

:::note info
**インデックスアクセス型の構文**
**T[K]**　T型のプロパティKにアクセスする
`T`: オブジェクト型
`K`: キー（プロパティ名）

インデックスアクセス型は、型に対して**ブラケット表記法**を使う。
:poop: ドット記法ではないよ！

<font color="red">**オブジェクト型["プロパティ名"];**</color>
<font col:three:or="red">**配列型[number]; （ 配列[インデックス]; ）**</color>

:::

### ◯◯型 と インデックスアクセス型

#### :one: オブジェクトの型 と インデックスアクセス型

```typescript:例）オブジェクトの型 と インデックスアクセス型
type Person = {
  name: string;
  age: number;
};

type AgeType = Person['age'];  // number型　キーを使って型を取得
```
```typescript:
let age: AgeType = 30;  // OK
age = '30';  // エラー（number型ではないため）
```

### :point_right: undefinedに気をつけて！！ 
オブジェクトに存在しないキーを使用すると undefined が返される。
```typescript:例）undefinedが返される
type Person = {
  name: string;
  age: number;
};

const person: Person = {
  name: "太郎",
  age: 30
};

function getPropVal(K: string) {
  return person[K];  // インデックスアクセス型
}

console.log(getPropVal("name"));   // "太郎"
console.log(getPropVal("age"));    // 30
console.log(getPropVal("unknownKey"));  // コンパイルエラーなし、undefined が返される
```
#### undefined対策  :arrow_right: 例1. undefinedを許容し、デフォルト値を設定しておく
```typescript:例）undefinedを許容し、デフォルト値を設定
type Person = {
  name: string;
  age: number;
};

const person: Person = {
  name: "太郎",
  age: 30
};

function getPropVal<K extends keyof Person>(key: K): string | number {
  return person[K] ?? "デフォルト値";  // person[K] が undefined なら "デフォルト値"
}

console.log(getPropVal("unknownKey"));  // "デフォルト値"（存在しないプロパティの場合）
```
`?? 演算子(Null合体演算子)`を使用して、null または undefined の場合にデフォルト値を設定し安全に扱う。

<font color="red">**ジェネリクスで使用するextends**</font>は、<font color="red">**型制約** </font>を意味する。
`K extends keyof Person`とする事で、K が Person 型のプロパティ名であることを制約。つまり、K は Person 型の name や age など、keyof Person のいずれかの値でなければならないという意味になる。
getPropVal関数の引数 key は Person 型のプロパティ名である必要があるため、`K extends keyof Person`として型引数 K を制限する。

#### undefined対策  :arrow_right: 例2. TSConfigでnoUncheckedIndexedAccessを設定する
インデックス型の時と同様

#### undefined対策  :arrow_right: 例3. keyof typeof で型安全を確保
:::note info
**keyof typeof を使う目的**
* オブジェクトからプロパティ名（キー）を型として動的に取得することで、
**ユニオン型**にすることができる。

* 保守性向上
オブジェクトの構造が変更されても、プロパティ名（キー）を型として取得するコードが自動的に更新されるため、コードの保守性が向上。

* 型安全を高める
**存在するプロパティ名（キー）のみ**を許可する型を作成できる。
:::

```typescript:例）keyof typeof を使った場合
const person = {
  name: "太郎",
  age: 30
};
// typeof person で person オブジェクト型 { name: string, age: number } が返される
// keyof で　このオブジェクト型の プロパティ名(キー) を取得
type PersonKeys = keyof typeof person;  // （リテラル型の）ユニオン型 "name" | "age"

function getPropVal(K: PersonKeys) {
  return person[K];  // インデックスアクセス型として動的に型を取得
}

console.log(getPropVal("unknownKey"));  // コンパイルエラー 'unkownKey' は割り当てられない
```
:::note
**リテラル型**
プリミティブ型（数値、文字列、bool値などの単純な値）の”特定の値だけを代入可能にする型”を設定できる。
```
let x: number;
x = 1;
```
↑ 数値なら 1 でも 100 でも代入できる
↓ <font color="red">**リテラル型**</font>を用いると、1 だけが代入可能な型が作れる。<font color="red">**厳密な型チェックになる**</font>
```
let x: 1;
x = 1; // OK
x = 100;　// エラー
```
:::

#### :two: 配列型とインデックスアクセス型

```typescript:例）配列型とインデックスアクセス型
type StringArray = string[];
type T = StringArray[number];
```
```typescript:
const arr: StringArray = ["north", "south", "west", "east"];
const direction: T = arr[1]; // "south" を返すので T は string として型推論される
```
:warning: **T は string 型なので、他の文字列を代入できる状態にある。** :warning:
これは、<font color="red">**インデックスアクセス型の危うさを持つ**</font>。


```
const direction: T = "northwest"; // 型エラーにならない
```
配列のインデックスアクセスで得られる型が広すぎるため、配列の具体的な要素の値を制限できない。意図しない代入を可能にしてしまう　→　<font color="red">**Widening（型の拡大）**</font>

```typescript
const arr = ["north", "south", "west", "east"] as const;
// arr の型は readonly ["north", "south", "west", "east"] となり、各要素はリテラル型
```
:::note info
**as const**
配列の要素を **リテラル型** として型推論させる事ができる。
* 配列内の要素が **固定の値** として扱われるようになる。
* 型が **リテラル型** としてイミュータブル（不変）な値になる
* 意図しない変更を防ぎ、型安全を強化できる
:::

**配列型[number]** に対して、`typeof` を使って、配列の型を取得し、インデックスアクセス型でリテラル型を導き出す。

```typescript
type DirectionFromArray = typeof arr[number];
// DirectionFromArray の型は、"north" | "south" | "west" | "east" のユニオン型
```
**<font color="red"> typeof 配列型[number] </font>** とすることで、arr 配列の各要素の型を **<font color="red"> リテラル型のユニオン型 </font>** （"north" | "south" | "west" | "east"）として取得できる。
```typescript
const direction1: DirectionFromArray = arr[0]; // "north" 型
const direction2: DirectionFromArray = arr[1]; // "south" 型
```

#### :three: タプル型とインデックスアクセス型

配列型 と タブル型の違い
|　|配列型|タプル型|
|:-:|:-:|:-:|
|型の要素|各要素が同じ型|各要素が異なる型|
|サイズ|可変|固定|

```typescript:例）配列型
let pets: string[] = ['dog', 'cat', 'bird']; // 各要素同じ型
pets.push('rabbit');
console.log(pets);   // ['dog', 'cat', 'bird', 'rabbit'] サイズが可変
```
```typescript:例）タプル型
let tuple: [number, string, boolean] = [20, "Hello", true];  // 異なる型の要素を持つ
tuple.push("New item"); // エラー：要素数が変わる
tuple[0] = "Not a number"; // エラー：number型に strign型 を割り当てれない
```

同じ型の値ばかりをタプルにして返却する関数の場合、区別するためにタプルにラベルを付けることができる。

```typescript:例）タプル型にラベル付け
const coordinate: [x: number, y: number, z: number] = [10, 20, 30];
```
## インデックス型 と インデックスアクセス型 の違い
|　|インデックス型|インデックスアクセス型|
|:-|:-|:-|
|書き方|[key: string]: type|ObjectType['key']|
|目的|オブジェクトのキーが動的で事前に決まっていない場合に、キーに対応する値の型を定義|オブジェクトの特定のキーにアクセスして、そのキーに対応するプロパティの型を取り出す|

## 参考

https://typescriptbook.jp/reference/values-types-variables/object/index-signature

https://typescriptbook.jp/reference/type-reuse/indexed-access-types

https://typescriptbook.jp/reference/type-reuse/keyof-type-operator

https://typescriptbook.jp/tips/generates-type-from-array

https://typescriptbook.jp/reference/values-types-variables/literal-types

https://typescriptbook.jp/reference/type-reuse/mapped-types

https://typescriptbook.jp/reference/tsconfig/nouncheckedindexedaccess

https://www.typescriptlang.org/docs/handbook/2/mapped-types.html

https://nakamuuu.blog/typescript-about-widening/

https://nakamuuu.blog/typescript-as-const-for-type-safety/

https://zenn.dev/uepon/articles/b9c799516ed155

https://zenn.dev/yskn_sid25/articles/da0547f3128308

https://qiita.com/bearone236/items/b52261cc83b2d1026e75

https://qiita.com/Bulgent/items/46078e8bc1cb7976616d

https://qiita.com/tak001/items/bec3ab7c1bb4df52a7e7

https://qiita.com/suzuki0430/items/f8d5cbbe9401c5945098

https://nishinatoshiharu.com/mapped-types-overview/