[TypeScript]型の基本とイミュータブルな追加・更新・削除 ～ 際限なき型地獄 ～

# [TypeScript]型の基本とイミュータブルな追加・更新・削除 ～ 際限なき型地獄 ～

　今回はTypeScriptの型の基本を扱っていきたいと思います。練習用の題材として、データ操作の基本中の基本、追加・更新・削除を行う関数をイミュータブルの形で実装していきます。

## 前提条件

- イミュータブルの形を崩さないように、狂ったようにreadonly  
一度生成したオブジェクトの書き換えは許さない
- interfaceにはプライマリーキー用とデータの用のインターフェースを用意  
データの構造は後から変えられるようにする
- addItemは自動的にidを採番するため、入力データはidがあっても無くても通るようにする  
もちろん他に関係ないプロパティが来たらエラー
- updateItemはidが必須、それ以外のデータは任意  
送られてきたプロパティだけ置き換え
- deleteItemはidが必須  
それ以外のデータは任意で関連プロパティはあってもエラーにはしない

　この条件を満たすための型設定を各所で行います

## ソースコード

```ts
//主キーを含むinterface
interface Primary {
  readonly id: number;
}

const addItem = <T extends Primary>(
  items: readonly T[],
  item: Omit<T, keyof Primary> & Partial<Primary>
) => {
  const id = items.reduce((max, item) => Math.max(max, item.id), 0) + 1;
  return [...items, { id, ...item }];
};

const replaceItem = <T extends Primary>(
  items: readonly T[],
  item: Partial<T> & Primary
) => {
  return items.map(v => {
    return v.id === item.id ? { ...v, ...item } : v;
  });
};

const delItem = <T extends Primary>(items: readonly T[], item: Partial<T> & Primary) => {
  return items.filter(v => {
    return v.id !== item.id;
  });
};

//データ用interface
interface Weapon extends Primary {
  readonly name: string;
  readonly cost: number;
}
//初期データ
let weapons: readonly Weapon[] = [
  { id: 1, name: "竹槍", cost: 10 },
  { id: 2, name: "棍棒", cost: 40 },
  { id: 3, name: "銅の剣", cost: 120 }
];

console.log("------------ 元データ ------------------");
console.log(JSON.stringify(weapons, null, " "));

console.log("----------- 追加(鋼の剣) ---------------");
weapons = addItem(weapons, { name: "鋼の剣", cost: 1000 });
console.log(JSON.stringify(weapons, null, " "));

console.log("---------- 更新(竹槍値上げ)-------------");
weapons = replaceItem(weapons, { id: 1, cost: 10000 });
console.log(JSON.stringify(weapons, null, " "));

console.log("----------- 削除(棍棒) -----------------");
weapons = delItem(weapons, { id: 2 });
console.log(JSON.stringify(weapons, null, " "));
```

## 実行結果

```sh
------------ 元データ ------------------
[
 {
  "id": 1,
  "name": "竹槍",
  "cost": 10
 },
 {
  "id": 2,
  "name": "棍棒",
  "cost": 40
 },
 {
  "id": 3,
  "name": "銅の剣",
  "cost": 120
 }
]
----------- 追加(鋼の剣) ---------------
[
 {
  "id": 1,
  "name": "竹槍",
  "cost": 10
 },
 {
  "id": 2,
  "name": "棍棒",
  "cost": 40
 },
 {
  "id": 3,
  "name": "銅の剣",
  "cost": 120
 },
 {
  "id": 4,
  "name": "鋼の剣",
  "cost": 1000
 }
]
---------- 更新(竹槍値上げ)-------------
[
 {
  "id": 1,
  "name": "竹槍",
  "cost": 10000
 },
 {
  "id": 2,
  "name": "棍棒",
  "cost": 40
 },
 {
  "id": 3,
  "name": "銅の剣",
  "cost": 120
 },
 {
  "id": 4,
  "name": "鋼の剣",
  "cost": 1000
 }
]
----------- 削除(棍棒) -----------------
[
 {
  "id": 1,
  "name": "竹槍",
  "cost": 10000
 },
 {
  "id": 3,
  "name": "銅の剣",
  "cost": 120
 },
 {
  "id": 4,
  "name": "鋼の剣",
  "cost": 1000
 }
]

``` 

## 解説

- AddItemでやっている型定義  

```
item: Omit<T, keyof Primary> & Partial<Primary>
  //Omit<T, keyof Primary> //TからPrimary(id)を除去
  //& Partial<Primary> //idを任意にして結合
```

- replaceItem、deleteItemでやっている型定義

```
item: Partial<T> & Primary
  //Partial<T> //Tのプロパティを任意にする
  //& Primary  //Primary(id)を必須にして結合
```

## 地獄

　たかが三種類の基本データ操作を行うだけで、どれだけ型の構造を書いたら良いのでしょうか？たぶんまだまだ抜け落ちがあることでしょう。extendsの三項演算子も使いたかったのですが、今回は出番がありませんでした。ということで、まだまだ型の深淵には程遠い状態です。

　今回は普通にletを使っていますが、let禁止、any禁止、readonly必須の縛りプレイでプログラムを組んだら、地獄どころかオーバーキルで昇天し、気がついたら天国へ逝けるかもしれません。

　私には天国は早すぎるので、しばらくは地獄の住人として暮らしていきたいと思います。