TypeScriptの型定義がわからなくなったので整理する

## はじめに
4月から新しい現場で初めてTypeScriptを書いている。

ざっくり概要はわかっているつもりだが、「なんかよくわからないけど型のエラーが出る」ということによく遭遇するので、自分の知識の整理のためにまとめた。

## 前提
TypeScriptの型は便利機能のうちの一つ。

TypeScriptで書かれたファイル（`.ts`）は、JavaScriptのファイル（`.js`）にコンパイルされ、実行時には`.js`ファイルが実行される。
コンパイルされた`.js`ファイルには型の情報は出力されない。

`.ts`ファイル（コンパイル前）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/55039cae-baca-f891-fb97-a5cb7e94cb53.png)

`.js`ファイル（コンパイル後）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/42f097c2-b930-467f-2f15-2d8f5dccb492.png)
→型の情報は入ってない。
JavaScriptにも型という概念は存在するが、型を宣言する構文は存在しない。

型をうまく使うことでバグを防げたり、開発を効率よくできたりする。
正しく理解することが大切。

## 基本的な型の定義
### 型定義のやりかた
TypeScriptの型を定義するには`変数名:型`とする。

```typescript
const hoge: string = "hoge";
let piyo: number = 0;
piyo = "piyo"; // エラーになる
```
定義した型とは違う値を代入しようとするとエラーになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/8f376af4-dcd1-5e06-b92b-a7665a240af3.png)

基本的な型として、上記の`string`、`number`のほか、`boolean`、`null`、`undefined`、`any`がある。
JavaScriptでのデータ型とほぼ同じだが、`any`はTypeScript独自の型。
どんな値でもいれることができる。

### 型推論
TypeScriptには型推論というものがあり、自動的に型を推測してくれる。
そのため、文字列や数値を変数にそのまま格納する分には、型宣言を書く必要がない。
基本的には型推論に任せるのがいいとされる。

型推論されている変数の例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/5f3fcfff-8b6a-a025-6f22-901b3fe2072e.png)
→`"hoge"`型になっている。
※`string`型になっているのと同じ。
`string`の中でも`"hoge"`という固定の文字列の型になっている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/734ab311-7089-53e2-ea5a-eb13e71a91c6.png)
→`number`型になっている。

ただし、変数宣言時に初期化しない場合、型は`any`型になってしまう。
`any`型はどんな値でも受け入れてしまうので好ましくない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/5d40d206-0322-3ea1-6ff9-3a8525760ff1.png)
→数値を入れても文字列を入れてもエラーにならない。

初期化しない場合は型を指定するべき。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/099c098b-464d-cb29-24c9-dc914ac06984.png)

## いろいろな型の定義

### 配列
配列は`型名[]`で定義できる。
```typescript
let arr: string[] = ["apple", "banana"];
arr.push("orange");
arr.push(100); //エラーになる
```
定義した型以外の値は入れられなくなる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/dd6947ac-4562-7164-fbfd-898ec6f6a1b5.png)

### オブジェクト
オブジェクトは`{プロパティ名:型名}`で定義できる。
```typescript
let person: {
  firstName: string;
  age: number;
  isValid: boolean;
} = {
  firstName: "John",
  age: 24,
  isValid: true,
};

// パラメータの不足
person = { //エラーになる
  age: 38,
  isValid: false,
};

// 存在しないパラメータ
person = {
  firstName: "Kevin",
  age: 30,
  isValid: true,
  country: "America", //エラーになる
};

// プロパティの型違い
person = {
  firstName: 0, //エラーになる
  age: 24,
  isValid: true,
};

```

`person`のあとに`:`(コロン)で続いているのがオブジェクトの型の定義、`=`(イコール)の後に続いているのが設定したいオブジェクトの値となる。

オブジェクトの型を定義することで、プロパティの過不足のチェックや、プロパティに設定されている値の型チェックを行うことができる。

定義通りのオブジェクトの形式でないとエラーになる。

#### プロパティの不足
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/fe1932dd-6df9-4629-5ebf-b4cfc9f3417c.png)

#### 存在しないプロパティ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/114a0c80-3a55-e81a-9ac0-c22fd50df303.png)

#### プロパティの型違い
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/6e8d8f34-291a-7faa-a45a-502e4e1794e7.png)

### 関数型
関数も型を設定することができる。
`function 関数名(変数名:型):戻り値の型{/* 関数の処理 */ }`
なお、戻り値の型には`void`という特殊な型を設定できる。
これは戻り値を返さない場合に設定する。

```typescript
function numToStr (num1:number):string {
    return num1.toString();
}

numToStr(1);

// 引数が少ない
numToStr(); //エラーになる

// 引数が多い
numToStr(1,2); //エラーになる

// 引数の型誤り
numToStr('2'); //エラーになる

// 戻り値の型誤り
function hello():string {
    return 100; //エラーになる
}

// 戻り値の型を設定しているのに返さない
function greeting():string { //エラーになる
    console.log('hello');
}

// 戻り値を返さない場合の型
function sayHi():void {
    console.log('Hi');
}
```

関数の型を定義することで、関数の呼び出しの際に引数の個数や型を指定することができる。
また、関数内で誤った型の値を返したり、値を返さなかったりすることを防げる。
型の定義にあった呼び出し方や、戻り値の定義をしていないとエラーになる。

#### 引数が少ない
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/29973a29-ac1f-936b-3c28-feb3d8cad668.png)

#### 引数が多い
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/7323fad9-a764-9da7-7194-a4f51c05a588.png)

#### 引数の型誤り
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/9af17448-9308-85a7-5883-c40770930dbf.png)

#### 戻り値の型誤り
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/6d1edadf-6f21-d2a1-a4e0-3f34ef4975d9.png)

#### 戻り値の型を設定しているのに返さない
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/4a5deac0-304c-0412-ecea-b3d768950855.png)


#### アロー関数での型の定義
アロー関数も型の定義方法は同じ。
`(引数:引数の型):戻り値の型`が関数の型定義時の形となる。
```typescript
const numToStr =  (num1:number):string => {
    return num1.toString();
}
```


### ユニオン型
`|`を使うことで、複数の型を組み合わせて型を定義することができる。
```typescript
let val : string | number;

val = 'hoge';
val = 100;
```
この場合、`val`は`string`か`number`のどちらかを満たしていればよい。
関数の引数が、文字列か数値のどっちかを受け取る、みたいなときに使える。

### 型エイリアス
型に名前をつけてオリジナルの型を作成できる。
ユニオン型はたいてい型エイリアスを使って定義する。オブジェクトも定義できる。
`type エイリアス名 = 型定義`とする。
定義したエイリアスは、`string`や`number`などの型と同じように、変数の型として設定できる。

```typescript
type NumOrStr = string | number;
type Person = { firstName: string; age: number; isValid: boolean };

const val: NumOrStr = "hoge";

const obj: Person = {
  firstName: "John",
  age: 28,
  isValid: true,
};
```
`NumOrStr`型と、`Person`型を作成して、それぞれ変数の型として設定している。
エイリアスを設定することで再利用性が上がるし、適切なエイリアス名をつけることで用途が明確になる。

### インターフェース
型の構造を定義することができる。
型エイリアスとにているが、インターフェースはオブジェクト型を定義するときに使う。

`interface インターフェース名 {}`とする。
```typescript
interface Person {
  firstName: string;
  age: number;
  isValid: boolean;
}

const obj: Person = {
  firstName: "John",
  age: 28,
  isValid: true,
};
```

インターフェースは`extends`キーワードを使うことで、他で定義した構造を引き継いで新しい構造を作ることができる。

```typescript
interface Person {
  firstName: string;
  age: number;
  isValid: boolean;
}

interface BusinessPerson extends Person {
  company: string;
}

const bizPerson: BusinessPerson = {
  firstName: "Kevin",
  age: 32,
  isValid: false,
  company: "ABC",
};
```

`Person`を`extends`して`BusinessPerson`という新たなインターフェースを作成している。
`BusinessPerson`のインターフェースには`firstName`プロパティなどはないが、`bizPerson`には設定できている。
これは、`Person`の構造を引き継いでいるためである。
ベースとなるものに新しい要素を追加することで型の定義を拡張することができる。

## まとめ
基本的な宣言方法から、型の種類についてざっと確認した。
最後にジェネリクスも紹介したかったが、これはまだ自身の理解が及んでいないので、もう少し理解できてから別枠で記事を書くことにする。

型をうまく使いこなせば、間違った方法で関数を呼ぶことを防げたり、オブジェクトの中身をある程度指定できたりするのは便利だと思った。

TypeScriptを使わない理由がないのでどんどん使い倒していきたい。