19
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-10-06

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

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

前提条件

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

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

ソースコード

//主キーを含む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, " "));

実行結果

------------ 元データ ------------------
[
 {
  "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必須の縛りプレイでプログラムを組んだら、地獄どころかオーバーキルで昇天し、気がついたら天国へ逝けるかもしれません。

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

19
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?