サバイバル TypeScript 2 -- オブジェクト、ReadOnly、インデックス型の使い方
Tags: typescript
Published: 2022-03-21 15:06:40


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

## なぜやるのか

業務で TypeScript を扱うプロジェクトに入り、オブジェクト配列の型の受け渡しで苦戦し質問したところ

> カエデさん TS のドキュメント基礎部分を100回読んでからこれ100周した方がいいっすよw

と言われたため。

# 本文

## オブジェクトはオリジナル

https://typescriptbook.jp/reference/values-types-variables/object/non-primitives-are-objects

```ts
const object1 = { value: 123 };
const object2 = { value: 123 };

console.log(object1 == object2); // false
```

オブジェクトは中身が同じでも別のものとして計算される。

---

## プロパティのフィールドとメソッド

```ts
const dog = {
  name: "Pochi",
  age: 3,
}
```

このように普通に key:value で変数と中身を入れるのはプロパティの中でもフィールドという

```ts
const object = {
  // キーと値に分けて書いたメソッド定義
  printHello1: function () {
    console.log("Hello");
  },
  // 短い構文を用いたメソッド定義
  printHello2() {
    console.log("Hello");
  },
};
```

一方、オブジェクトのプロパティとして関数を入れると「メソッド」になる

```ts
functionName: function () {}
```

のように key: value で渡すこともできるが

```ts
functionName() {}
```

のように中に関数定義を普通に書くこともできる

両者は全く同じもの（プロパティの中のメソッド）として扱われる

フィールドとメソッドは正確には別物で、メソッドに null　を上書きするとフィールドになり、引数を渡せなくなる。

---

## 型解釈

```ts
let box: { width: number; height: number };
```

オブジェクトのプロパティは、中身を入れる前にキーと型のペアを定義できる
これを 型解釈 ( type annotation ) という
型解釈ではカンマではなくセミコロンで区切るのが推奨されている

```ts
type Box = { width: number; height: number };
let box: Box = { width: 1080, height: 720 };
```

このように type を使って型を先に宣言する方法もある
これは型エイリアスという。

---

## 型エイリアス

型に名前をつけるもの。型の変数と解釈した。

```ts
type StringOrNumber = string | number;
```

この例がわかり易い。`string | number` を入れる型の変数。

```ts
type OK = 200;
type UserObject = { id: number; name: string };
type CallbackFunction = (value: string) => boolean;
```

こうやって定義できる。
こうすることで、長くなる式を変数に入れて再利用するように、型も再利用できる。

---

## 読み取り専用 ReadOnly のオブジェクトでの動作

```ts
let obj: {
  readonly foo: number;
};
```

オブジェクトのプロパティの型解釈も読み取り専用で定義できる。
何も入ってない状態ですら。

```
obj.foo = 2
Cannot assign to 'foo' because it is a read-only property.ts(2540)
```

普通にそのプロパティに数値を入れようとするとエラーになる。

```ts
obj = {foo: 3}
```

しかし、{ key: value } でペアとして入れると入る。よくわからない

```ts
let obj: Readonly<{
  a: number;
  b: number;
  c: number;
}>;
```

また、ReadOnly<T> で括ることで

```
Cannot assign to 'a' because it is a read-only property.ts(2540)
Cannot assign to 'b' because it is a read-only property.ts(2540)
Cannot assign to 'c' because it is a read-only property.ts(2540)
```

まとめてブロックできる

```ts
let school: {
  readonly headTeacher: {
    name: string;
  };
};
school.headTeacher.name = "Masa"
```

このコードでは、headTeacher の校長自体は変更できないが
校長プロパティである名前は変更できてしまう。

読み取り専用のプロパティのさらに中のプロパティは読み取り専用にならない
読み取り専用は再帰的ではないということだ。

https://typescriptbook.jp/reference/values-types-variables/object/readonly-vs-const

```ts
let obj: { readonly x: number } = { x: 1 };
obj = { x: 2 }; // 許可される
```

また、変数の中身を丸ごと書き換えることはできてしまう。
だからプロパティを書き換えられちゃう const の上位互換というわけでもない。

---

## 中に入るプロパティの数を自由にするインデックス型

```ts
let　company: {
  [K: string]: number;
};
```

このように　key を具体的に指定せずに、string とだけ書くものをインデックス型という。

```ts
let person: {
  age: number;
  power: number
}
person.kinniku: 100
```

普通は TS では先に定義したものしか入れられない。
この例だと

> Property 'kinniku' does not exist on type '{ age: number; power: number; }'.ts(2339)

筋肉は存在しないと出てしまう

```ts
let company: {
  [K: string]: number;
};

company.age = 300
company.profit = 1_000_000_000
company.members = 30
```

しかし、インデックス型だと、数値を値にとるプロパティだったらいくらでも作ることができる。

```ts
let house: Record<string, number>;

house.age = 15
house.areaM2 = 30
house.monthlyPrice = 80_000
```

同じようにインデックス型を Record<> を使っても定義することができる。