Typescriptでの実行環境構築と基本的な使い方

## はじめに
業務でTypescriptを初めて使うにあたり、基礎的なことについて学習したので記事にまとめてみました。
この記事では、在庫管理アプリをテーマにTypeScriptの実行環境の構築から、型宣言やジェネリクスといった基本的な概念を解説します。

## 環境
 - Volta（1.1.1）
 - Node（v22.7.0）

## TypeScriptの実行環境構築
最初にTypeScriptを実行するための環境を構築していきます。

#### 1. package.jsonファイルを作成する
```shell
$ npm init --yes
```

#### 2. 「typescript」「ts-node」をインストールする
```shell
$ npm i ts-node typescript
```

#### 3. tsconfig.jsonを作成する
```shell
$ npx tsc --init
```
これでTypeScriptの実行環境が整いました。

## コードの全体像
```typescript
//　~~~~~~~~~ 定義部分 ~~~~~~~~~~~

type Item = {
  id: number;
  name: string;
  detail: string;
};

type newItem = {
  id: number;
  name: string;
  status: 'taking' | 'returnd';
};

let items: Item[] = [
  {
    id: 1,
    name: 'スマホ',
    detail: '64GB'
  },
  {
    id: 2,
    name: 'ディスプレイ',
    detail: '28インチ'
  },
  {
    id: 3,
    name: 'ノートパソコン',
    detail: '15インチ'
  }
];

let newItemList: newItem[] = [];

// items配列にアイテムを追加する関数
function addItem(itemName: string, itemDetails: string): void {
  const newItem = {
    id: items.length + 1,
    name: itemName,
    detail: itemDetails
  };

  items.push(newItem);
}

// 配列内のアイテムを削除する関数
function deleteItem(targetItem: string | number): Item[] {
  const item =
    typeof targetItem === 'number'
      ? items.filter((item) => item.id !== targetItem)
      : items.filter((item) => item.name !== targetItem);

  if (!item) {
    console.error('アイテム情報が見つかりませんでした');
    return items;
  }

  return item;
}

// 配列内のアイテム情報を更新する関数
function updateItem(
  targetId: number,
  UpdateTarget: '名前' | '詳細',
  nameOrDetail: string
): Item[] {
  const item = items.find((item) => item.id === targetId);
  if (!item) {
    console.error('アイテム情報が見つかりませんでした');
    return items;
  }

  console.log('\n\n更新前のアイテム情報');
  console.log(item);

  switch (UpdateTarget) {
    case '名前':
      item.name = nameOrDetail;
      break;
    case '詳細':
      item.detail = nameOrDetail;
      break;
  }

  console.log('\n\n正常に更新されました');
  console.log(item);

  return items;
}

// newItemListにアイテムを追加する関数
function addNewItem(
  item: Omit<Item, 'detail'>,
  inputStatus: 'taking' | 'returnd'
): void {
  const updatedItem = {
    ...item,
    status: inputStatus
  };

  newItemList.push(updatedItem);
}

// itemsまたは、newItemListにアイテムを追加できる関数
function addManagedTarget<T>(array: T[], manegedTarget: T) {
  array.push(manegedTarget);
}

//　~~~~~~~~~ 処理部分 ~~~~~~~~~~~

// items配列にアイテムを追加する関数
addItem('マウス', 'ワイヤレス');
console.log('\nアイテム追加');
console.log(items);


// 配列内のアイテムを削除する関数
items = deleteItem(3);
console.log('\n\nアイテム削除');
console.log(items);


// 配列内のアイテム情報を更新する関数
updateItem(1, '名前', 'スマートフォン');


// newItemListにアイテムを追加する関数
addNewItem(
  {
    id: 1,
    name: 'スマホ'
  },
  'taking'
);
console.log('\n\nnewItemListにアイテムを追加');
console.log(newItemList);


// itemsまたは、newItemListにアイテムを追加できる関数
addManagedTarget(items, {
  id: 8,
  name: 'キーボード',
  detail: 'メカニカル式'
});
console.log('\n\nアイテムを追加');
console.log(items);
```

## 型宣言
TypeScriptでは、変数や関数の引数、戻り値に対して明示的に型を指定できます。

```typescript
type Item = {
  id: number;
  name: string;
  detail: string;
};

// ~~~ 省略 ~~~

let items: Item[] = [
  {
    id: 1,
    name: 'スマホ',
    detail: '64GB'
  },
  {
    id: 2,
    name: 'ディスプレイ',
    detail: '28インチ'
  },
  {
    id: 3,
    name: 'ノートパソコン',
    detail: '15インチ'
  }
];

function addItem(itemName: string, itemDetails: string): void {
  const newItem = {
    id: items.length + 1,
    name: itemName,
    detail: itemDetails
  };

  items.push(newItem);
}

// ~~~ 省略 ~~~

addItem('マウス', 'ワイヤレス');
console.log('\nアイテム追加');
console.log(items);
/*
出力結果：

アイテム追加
[
  { id: 1, name: 'スマホ', detail: '64GB' },
  { id: 2, name: 'ディスプレイ', detail: '28インチ' },
  { id: 3, name: 'ノートパソコン', detail: '15インチ' },
  { id: 4, name: 'マウス', detail: 'ワイヤレス' }
]
*/
```
この例では、Itemというオブジェクト型を宣言し、Item型で、itemsの配列を定義しています。また、itemsの配列にitemを追加するための関数を定義しています。この関数の戻り値の方にvoid型を指定しています。void型は戻り値がない時に明示的に戻り値がないことを示す型です。


## リテラル型とユニオン
リテラル型とは、型に直接リテラルを指定する型のことです。
また、ユニオンとは、複数の型の受け取りを許容する概念を指します。
```typescript
type newItem = {
  id: number;
  name: string;
  status: 'taking' | 'returnd';
};
```
この例では、newItem型のstatusプロパティで特定のリテラル型（'taking'または'returnd'）のみを受け付けるように定義しています。


## Narrowingとタイプガード
Narrowingとは、型によって処理を変える手法のことです。
次に、タイプガードは、undifinedを回避するための処理になります。
```typescript
function deleteItem(targetItem: string | number): Item[] {
  // Narrowing
  const item =　　
    typeof targetItem === 'number'
      ? items.filter((item) => item.id !== targetItem)
      : items.filter((item) => item.name !== targetItem);

　  // タイプガード
  if (!item) {
    console.error('アイテム情報が見つかりませんでした');
    return items;
  }

  return item;
}

// ~~~ 省略 ~~~

items = deleteItem(3);
console.log('\n\nアイテム削除');
console.log(items);
/*
出力結果：

アイテム削除
[
  { id: 1, name: 'スマホ', detail: '64GB' },
  { id: 2, name: 'ディスプレイ', detail: '28インチ' },
  { id: 4, name: 'マウス', detail: 'ワイヤレス' }
]
*/
```
この例では、指定したアイテム情報を削除する処理を定義しています。
引数で受け取った値をfilterメソッドの条件で使用し、条件に一致しなかった場合、undifinedが返されます。
このままでは、後続の処理でエラーが発生してしまうため、タイプガードを使用し、事前にエラーを回避しています。

## ユーティリティ型（Omit）
ユーティリティ型とは、既存の型を操作し、再利用することができる特別な型です。
また、種類が複数あり、その中で代表的なユーティリティ型の一つにOmitというものがあります。
Omitは、既存の型から特定のプロパティを除外して利用することがきる型です。

```typescript
type newItem = {
  id: number;
  name: string;
  status: 'taking' | 'returnd';
};

// ~~~ 省略 ~~~

let newItemList: newItem[] = [];

// ~~~ 省略 ~~~

function addNewItem(item: Omit<Item, 'detail'>, inputStatus: 'taking' | 'returnd'): void {
  const updatedItem = {
    ...item,
    status: inputStatus
  };

  newItemList.push(updatedItem);
}

// ~~~ 省略 ~~~

addNewItem(
  {
    id: 1,
    name: 'スマホ'
  },
  'taking'
);
console.log('\n\nnewItemListにアイテムを追加');
console.log(newItemList);
/*
出力結果：

newItemListにアイテムを追加
[ { id: 1, name: 'スマホ', status: 'taking' } ]
*/
```
この例では、Item型のdetailプロパティを除外したオブジェクトとstatusプロパティをくっつけて新しいオブジェクトを作成し、newItem型の配列に追加する処理を行っています。

## ジェネリクス
ジェネリクスは、どんな型にも対応できる汎用的な型になります。

```typescript
type Item = {
  id: number;
  name: string;
  detail: string;
};

// ~~~ 省略 ~~~

let items: Item[] = [
  {
    id: 1,
    name: 'スマホ',
    detail: '64GB'
  },
  {
    id: 2,
    name: 'ディスプレイ',
    detail: '28インチ'
  },
  {
    id: 3,
    name: 'ノートパソコン',
    detail: '15インチ'
  }
];

let newItemList: newItem[] = [];

// ~~~ 省略 ~~~

// itemsまたは、newItemListにアイテムを追加できる関数
function addManagedTarget<T>(array: T[], managedTarget: T): void {
  array.push(managedTarget);
}

// ~~~ 省略 ~~~

addManagedTarget<Item>(items, {
  id: 8,
  name: 'キーボード',
  detail: 'メカニカル式'
});
console.log('\n\nアイテムを追加');
console.log(items);
/*　
出力結果：

アイテムを追加
[
  { id: 1, name: 'スマートフォン', detail: '64GB' },
  { id: 2, name: 'ディスプレイ', detail: '28インチ' },
  { id: 4, name: 'マウス', detail: 'ワイヤレス' },
  { id: 8, name: 'キーボード', detail: 'メカニカル式' }
]
*/
```
この例では、パラメータに渡された配列と同じ型のオブジェクトを追加する処理を行っています。


## まとめ
この記事では、TypeScriptの型システムを使った基本的な使い方を紹介しました。型宣言、リテラル型、ジェネリクスなど、TypeScriptの強力な型システムを活用することで、より安全でメンテナンスしやすいコードを記述できます。

TypeScriptの型システムは柔軟性があり、さらに複雑な型を扱うことができるため、プロジェクトに応じて最適な型を選択して活用しましょう。

## リポジトリ
https://github.com/dai-gi/invetory-management

## 参考記事
https://qiita.com/Sicut_study/items/340022c2a7bc05b15587