TypeScriptの基本型について、簡単にまとめてみた

# はじめに
[公式ドキュメント](https://www.typescriptlang.org/docs/home)を参考に、TypeScriptの基本型についてまとめてみました。
全体をざっと理解するのに役に立つと思います。


# 代表的なもの
## boolean
論理値を扱う型です。`true`または`false`の値を格納できます。

```ts
// 変数宣言
let isSuccess: boolean;

// リテラル表現
isSuccess = true;
isSuccess = false;
```

## number
数値を扱う型です。整数と小数の両方を格納できます。


```ts
// 変数宣言
let num: number;

// リテラル表現
num = 0;        // 10進数
num = 0x1234;   // 16進数
num = 0o744;    //  8進数（ECMAScript2015で対応）
num = 0b0101;   //  2進数（ECMAScript2015で対応）
num = 1.234;    // 小数もOK
num = NaN;      // 非数
num = Infinity; // 無限大
```

内部的には、IEEE 754の64ビット倍精度浮動小数点として値を保持しています。
ですので、丸め誤差を生じない整数の範囲が $-2^{53} $ ～ $2^{53}$ となることに注意が必要です。

これを超える整数を扱う場合は、丸め誤差を許容するか、それ以上の整数が扱える別の手段（`BigInt`や`big.js`など）に頼る必要があります。

## string
文字列を扱う型です。

```ts
// 変数宣言
let str: string;

// リテラル表現
str = "hoge";  // ダブルクォーテーション、または
str = 'hoge';  // シングルクォーテーションで囲う

// テンプレート文字列（``）を使えば、途中で改行したり、式を埋め込むことが可能です。
let myName: string = 'Taro';
let profile: string = `Hello.
  My name is ${myName}.`;

// 文字列の連結もできます。
let profile: string = 'Hello.\n' + 'My name is ' + myName;
```

## array
複数の同じ型のデータをまとめて扱う型です。

```ts
// 変数宣言
let list0: number[];      // []を付けるだけ
let list1: Array<number>; // Array<>でもOK

// リテラル表現
list0 = [0, 1, 2];
```

## tuple
複数のデータをまとめて扱う型です。
配列とは異なり各要素が同じ型である必要はありません。

```ts
// 変数宣言
let data: [string, number];  // []の中に型を書く

// リテラル表現
data = ['hoge', 0];
```

## enum
列挙型です。

```ts
// 宣言
enum Animal {
  Dog,
  Cat,
  Horse,
}

// 使い方
let myPet: Animal = Animal.Dog;
```

各要素に、好きな数値を割り当てることができます。

```ts
enum Animal {
  Dog = 1,     // 1を割り当てる
  Cat,         // 2を割り当てる（省略した場合は、前の値+1）
  Horse = 10,  // 10を割り当てる
}
```

配列のようにアクセスすることで、数値から要素に変換ができます。

```ts
console.log(Animal[1]);      // 1 -> 'Dog'
```

同様のやり方で、その逆もできます。

```ts
console.log(Animal['Dog']);  // 'Dog' -> 1
```

## object
他の型を内包した型です。`{`と`}`で表現します。


```ts
let hoge: object = {
  fuga: 0,
  piyo: 'a'
};
```

# ちょっと特殊なもの

## any と unknown
すべての型を代入できる型です。

```ts
// 数値や文字列など、なんでも代入できます。
let a: any;
a = 0;
a = "a";

// unknownも同様です。
let u: unknown;
u = 0;
u = "a";
```

`any`と`unknown`の違い：

- `any` : とくに制約なく変数を参照できる。
- `unknown` : 型を明らかにしないと参照できない。

```ts
// any型だと
let a: any = 'a';
a++;      // -> おっと。。 aはNaNに。

// unknownだと
let u: unknown = "a";
u++;      // コンパイルエラーになる

if (typeof u === "number") {
  u++;    // 型ガードを使って、型を明示すればエラーにはならない
}
```

`any`を、安全にしたものが`unkown`とも言えますね。

### 注意点

`any`と`unknowon`は一見便利ですが、本当に必要なとき以外は使うべきではありません。

何でも代入できるということは、それを参照する際にあらゆる可能性を考慮すべきであるとも言えます。せっかくの静的型付けの特徴を台無しにしてしまうので、他に手段がない限りは使わないようにしましょう。

## void
どの型も持たない型です。
すべての型の性質を持つ、`any`や`undefined`とは対照的な存在になります。

関数において戻り値がないことを示すときに使われるのみです。

```ts
function hoge(): void {
  console.log("This is function.");
}
```

変数の型としても使えますが、`undefined`と`null`しか代入できないため実質的に意味がありません。

```ts
// なんの意味もない
let hoge: void = undefined;
```

## undefined と null

`undefined`は初期化されていない状態を表し、`null`は何もない状態を表します。

ややこしいですが、`undefined` と `null` は、それぞれ同名の `undefined型` と `null型` という型を持っています。`undefind型`には、`undefined`しか格納できませんし、`null型`には`null`しか格納できません。

```ts
// これしか格納できない
let hoge: undefined = undefined;
let fuga: null = null;
```

### 使い道

`undefined`と`null`は、他の変数にも代入することが可能です。

```ts
let hoge: number = undefined;
let fuga: string = null;
let piyo: any    = null;
```

このようにすることで、その変数が未初期化であることや、空っぽであることを表現することができます。

ただし、この使い方はオススメできません・・・。

`undefined`と`null`がすべての変数に代入可能であるということは、プログラマは「あらゆる変数に対して、`undefind` と `null`である可能性」を考慮しないといけないことになります。

これは、過剰チェックやチェック漏れに繋がります。

そこで次の仕組みを使い、`undefined`と`null`の代入に制限をかけておきましょう。

### 厳密なnullチェック（strictNullChecks）

.tsconfigの、`strictNullChecks`を`true`にします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/839411/18daddda-c9d1-3872-6b96-0725d56cccc9.png)
こうすることで、

- `null値`を代入できるのは、`unknown型`, `any型`, `null型`のみ
- `undefind値`を代入できるのは、`unknown型`, `any型`, `undefied型`のみ

となります。

これとunion（複数の型を持てる機能）とを組み合わせると、以下のように記述できます。

```ts
let a: number | undefind | null;  // aには、undefindもnullも格納できる。
let b: number;                    // bには、undefindやnullは格納できない。
```

これで`undefind`/`null`チェックを最低限に抑えることができ、プログラマがより生産的なことがらに集中できるようになります。

また変数を宣言するタイミングで、「`null`が入り得るか？」を考えるプロセスが自然と生まれることも、この機能のメリットのひとつと考えています。こういった背景から、`strictNullChecks`を有効にしておくことを強くお勧めします。

## never
決して発生しない値を表す型です。

以下のような関数の、戻り値の型は`never`です。

```ts
function hoge(): never {
  throw new Error();

  // ここには来ない
}

function fuga(): never {
  while (true) {}

  // ここには来ない
}
```


（2020/11/8 @sdkei様のコメントを受けて追記）
`never`型の変数には、決して値が代入されることはありません。
もしも値を代入するようなコードを書いたら、（コンパイラが検出できる範囲において）コンパイルエラーとなってしまいます。

この特徴を利用し、`union`型の構成要素をすべて網羅しているコードであることを、コンパイル時に保証する使い方があります。

たとえば、次のような例です。

```ts
type PrimaryColor = "red" | "green" | "blue" | "alpha";

function logColorValue(color: PrimaryColor): void {
  switch (color) {
    case "red":
      console.log(0x00FF0000);
      break;
    case "green":
      console.log(0x0000FF00);
      break;
    case "blue":
      console.log(0x000000FF);
      break;
/*
    case "alpha":
      console.log(0xFF000000);
      break;
*/
    default:
      /*
        次の行がコンパイルエラーになり、alphaに対するcaseが漏れていることに気づける。

        エラーになる理由：
          colorが"alpha"のときに、never型の変数に「値が代入されてしまう」ため。
          ちゃんと case alpha を記述することで値が代入される可能性がなくなり、エラーが解消される。
      */
      const unexpected: never = color;
  }
}
```

`switch`-`case`だけでなく、`if`-`else if`-`else` においても利用できます。

## 型アサーション（Type Assertion）
TypeScriptのコンパイラに対して、型を明示的に示すことが出来ます。
他言語によくある「キャスト」と似ていますが、実行時の動作には影響なく、TypeScriptのコンパイラのみで使用されます。

２通りの記述方法があり、それぞれ全く同じ動作をします。

```ts
let hoge: unknown = 'hoge';
let str: string;

// asを使う方法
str = hoge as string;

// <>を使う方法
str = <string>hoge;
```

ですが、一般的に`<>`よりは`as`を使うことが推奨されます。
（JSXでは、`as`のみが使えるようになっているから）。

# 参考にしたサイト
Official : [https://www.typescriptlang.org/](https://www.typescriptlang.org/)
TypeScript Deep Dive : [https://basarat.gitbook.io/typescript/](https://basarat.gitbook.io/typescript/)
JavaScript | MDN Web Docs : https://developer.mozilla.org/ja/docs/Web/JavaScript