TypeScriptのオブジェクトリテラルとオブジェクト型を理解したい

## オブジェクトリテラルとは

MDNによると、

>オブジェクトは new Object()、Object.create()、リテラル表記法 (初期化子表記法) を使用して初期化されます。オブジェクト初期化子はオブジェクトのプロパティ名と関連した値のゼロ以上のペアのリストで、中括弧 ({}) で囲まれます

オブジェクトリテラル(オブジェクト初期化子)とは表記法のことです。

# JSONとの違い

JavaScriptのオブジェクトリテラルをベースに作られた軽量データフォーマットがJSONですが、
オブジェクトリテラルとJSONでは、記載方法が若干違います。

:::note warn
JSONだと
・プロパティ名と文字列はダブルクォートで囲む。
・関数の値を割り当てることができない。
:::

これだけではないですが、主にこのような違いがあります。

# 構文について

#### TypeScriptのオブジェクトは連想配列です。

```TypeScript
const obj = {
    a: "hello",
    b: 123 
}
```

上記のようにプロパティ名 (a) に対して値 ("hello") を定義して、
プロパティの定義はコンマ(,)で区切ります。


#### ・値の取り出し
取り出すときは、オブジェクトに対してプロパティ名を指定します。
ドット表記法やブラケット表記法が使用できます。

```TypeScript
// ドット表記法
console.log(obj.a); // -> hello
// または
// ブラケット表記法
console.log(obj["a"]); // -> hello　
```

ネスト(入れ子)にも対応できます。

```TypeScript
const obj = {
    a: "hello",
    b: 123,
    c: {
        d: 456
    }
}
console.log(obj.c.d); // -> 456
```


#### ・値の代入
値を代入するときも、同じようにオブジェクトに対してプロパティ名を指定します。

```TypeScript
const obj = {
    a: "hello",
    b: 123 
}

obj.a = "hoge"
console.log(obj.a); // -> hoge
```

ちなみにconstで宣言していますが、実は同じオブジェクト扱いなので値を代入することは可能です。
ただこちらは、あまりやらない方がいいかなとは思います。

TypeScriptでは、constアサーションでreadonly(書き換え不可)にできます。
オブジェクトの末尾にas constを付けて宣言します。

```TypeScript
const obj = {
    a: "hello",
    b: 123 
} as const

obj.a = "hoge"
console.log(obj.a);
```

```
error TS2540: Cannot assign to 'a' because it is a read-only property.
```

このように書き換え不可とされているところへ値を代入しようとした結果、
コンパイル(トランスパイル)時にエラーとなります。

#### ・変数でプロパティ名と値を省略

変数を用いてプロパティ名と値を省略できます。
変数名がプロパティ名で中身がその値となります。

```TypeScript
const a: string = "hello";
const b: number = 123;

const obj = {
    a,
    b
}

console.log(obj); // -> { a: 'hello', b: 123 }
```


#### ・プロパティ名に数値を使う
プロパティ名は数値で記載することも可能です。

```TypeScript
const obj = {
    12: "hello",
    0.34: 123 
}

console.log(obj[12]); // -> hello
console.log(obj); // -> { '12': 'hello', '0.34': 123 }　
```

このように数値として記載できますが、実際のプロパティ名は文字列です。



#### ・プロパティ名を動的に決める

プロパティ名は動的にも決められます。
プロパティ名に[変数名] のように記載します。

```TypeScript
const hoge: string = "a";
const fuga: number = 12;

const obj = {
    [hoge]: "hello",
    [fuga]: 123
}

console.log(obj); // -> { '12': 123, a: 'hello' }
console.log(obj.a); // -> hello
```


#### ・functionを宣言する

オブジェクトリテラル内でfunctionを宣言することもできます。
取り出すときは、オブジェクトに対してfunctionを指定します。

```TypeScript
const obj = {
    a: "hello",
    b: 123,
    hoge() {
        return "hogehoge"
    }
}

console.log(obj.hoge()); // -> hogehoge
```

#### ・別のオブジェクトをプロパティに追加する

オブジェクトリテラルの[スプレッド構文](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax)を使うと、別のオブジェクトをプロパティを追加することができます。
「...オブジェクト名」 のように宣言します。

```TypeScript
const obj = {
    "a": "hello",
    "b": 123
}

const obj2 = {
    ...obj,
    "c": "hogehoge"
}

console.log(obj2); // -> { a: 'hello', b: 123, c: 'hogehoge' }
```

# オブジェクト型を宣言する

「type(型エイリアス) 型名 = 型」というように宣言します。

```TypeScript
type objtype = {
    a: string;
    b: number;
};

const obj: objtype = {
    a: "hello",
    b: 123
}

console.log(obj); // -> { a: 'hello', b: 123 }
```

interfaceでも可能ですが若干書き方が変わります。

```TypeScript
interface objtype {
    a: string;
    b: number;
}

type objtype = {
    a: string;
    b: number;
};
```

ざっくり言うとinterfaceは型の宣言で、
typeは宣言された型に別名を付与する感じでしょうか。

どっちを使うかの問題に関しては、色々と議論がなされていますが、
普通にアプリケーション開発する分にはtypeで良いかと思います。

#### ・変数からオブジェクト型を抽出する
TypeScriptのtypeofを使って、既存の変数からオブジェクト型を抽出することができます。
以下のように書きます。

```TypeScript
const obj= {
    a: "hello",
    b: 123
}

type objtype = typeof obj;

/* objtypeの中身
type objtype = {
    a: string;
    b: number;
}
*/
```

※JavaScriptのtypeof演算子とは別物なので注意

#### ・readonlyで読み取り専用にする

こちらはオブジェクト型で、
readonlyを付けて読み取り専用にすることもできます。

```TypeScript
type objtype = {
    readonly [key: string]: string;
};

const obj: objtype = {
    a: "hello",
    b: "hogehoge",
    c: "fugafuga",
}

obj.a = "piyopiyo";
```

```
error TS2542: Index signature in type 'objtype' only permits reading.
```

代入しようとするとエラーで怒られます。

ちなみにconstアサーションとreadonlyの違いは、
constアサーションはオブジェクトに対する宣言に対して、readonlyはプロパティごとに読み取り不可を付与することができます。

なので例えば、以下のような処理があるとします。

```TypeScript
type box = {
    c: string;
}

type objtype = {
    readonly a: string;
    readonly b: box;
};

const obj: objtype = {
    a: "hello",
    b: {
        c: "hogehoge"
    },
}

console.log(obj); // -> { a: 'hello', b: { c: 'hogehoge' } }

obj.b.c = "fugafuga";

console.log(obj); // -> { a: 'hello', b: { c: 'fugafuga' } }
```

この場合、aとbはreadonlyですがcはreadonlyではないので、
値を代入できてしまいます。
意図して行うのなら良いですが、十分気を付けましょう。

#### ・オプションプロパティの定義

こちらは必須ではないプロパティを宣言したいときに役立ちます。
プロパティ名の後ろに ? を追加します。

```TypeScript
type objtype = {
    a: string;
    b: string;
    c?: string;
};

const obj: objtype = {
    a: "hello",
    b: "hogehoge",
}

// エラーにならない
console.log(obj); // -> { a: 'hello', b: 'hogehoge' }

obj.c = "fugafuga";

// あとから追加もできる
console.log(obj); // -> { a: 'hello', b: 'hogehoge', c: 'fugafuga' }
```

cのプロパティは宣言しませんでしたが、エラーにはなりません。
必要になったときに後から追加することも可能です。

## インデックスシグネチャ
オブジェクト型の宣言で、プロパティ名を固定化せずに複数のプロパティを定義したい場合に有効な方法です。
[ キー名: 型 ]: 型; というように宣言します。


```TypeScript
type objtype = {
    [key: string]: string;
};

const obj: objtype = {
    a: "hello",
    b: "hogehoge",
    c: "fugafuga"
}

console.log(obj); // -> { a: 'hello', b: 'hogehoge', c: 'fugafuga' }
```

キーは複数定義することもできます。

```TypeScript
type objtype = {
    [key: string]: string;
    [key2: number]: string;
};

const obj: objtype = {
    a: "hello",
    b: "hogehoge",
    c: "fugafuga",
    123: "piyopiyo"
}

console.log(obj); // -> { '123': 'piyopiyo', a: 'hello', b: 'hogehoge', c: 'fugafuga' }
```

## まとめ
オブジェクトリテラルとオブジェクト型を組み合わせると
かなり奥が深いですね。

執筆にあたり色々と調べたのですが、
知らないことが多かったので色々試せて良かったです。

この記事がどなたかの参考になれば幸いです。

## 参考
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Object_initializer

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Property_Accessors

https://typescript-jp.gitbook.io/deep-dive/type-system#jenerikkusugenerics

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