5分間でTypeScriptの基礎的な型を理解して使えるようになる

今回はTypeScriptで最も重要となる「型の基礎」について説明していきたいと思います。ここでの「型」とは、データの性質のようなもので、どのように取り扱うべきかの定義だと考えてもらえばいいかと思います。

以下、TypeScriptで扱う型について記述していきます。

## boolean

真偽値型ともいい、`true`/`false` の2つの値があります。条件式などでよく用いられます。

下のように宣言します。

```tsx
let isChecked: boolean = true;
// - ①
let isDone: true = true;
// - ②
let isError: true = false;
// - ③コンパイルエラー
```

TypeScriptでは型を宣言する時に、`:` を用いて、その右側に型を指定します。型を指定することを**型アノテーション(type annotation)**と呼びます。

①は`boolean`型を明示的に伝えており、変数`isChecked`には`true`/ `false`の値を入れることができます。

②には値が特定の`boolean`であること、今回は`true`であることを明示的に伝えています。このように、特定のただ一つの値を表し、それ以外の値は受け入れない型のことを**リテラル型(literal type)**と呼びます。

そのため、③の例では`isError`は`true`とTypeScriptに明示的に伝えたにも関わらずfalseを代入してしまっているのでコンパイルエラーが発生しています。

## number

`number`は整数、浮動小数点、Infinity、Nanなど、全ての数字の集合です。加減算や剰余など、数値に関する計算などをサポートします。

```tsx
let num: number = 123;
// - ①
let fixedNum: 45.6 = 45.6;
// - ②
```

①の例は変数numが`number`であることを宣言しています。

②の例では値が特定の`number`であることを宣言しています。当然、異なる数値を代入するとコンパイルエラーが発生します。

## bigint

`bigint`は`number`で扱えなかったより大きな整数を扱うことができます。具体的には、2^53以上の数値を扱うことができます。

```tsx
let bigNum: bigint = 100n;
```

`bigint`を宣言したら値の最後に`n`をつけます。

## string

`string`は文字列を表します。以下のように宣言します

```tsx
let message: string = "こんにちは";
let nightMessage: "こんばんは" = "こんばんは"
```

## symbol

`symbol`はES2015で導入された機能であり、オブジェクトなどで誤った値が設定されたくない時などに用いられます。シンボルはイミュータブル(不変)であり、コンストラクタを呼び出すことで宣言できます。

```tsx
let key1: symbol = Symbol("hoge");
let key2: symbol = Symbol("hoge");

key1 === key2;
// false
let key3 = key1 + "fuga";
// エラー
```

`symbol`は一意なので、他のどの`symbol`とも等しくなりません。

また、`symbol`はイミュータブルなため値を変更することもできません。

## object

TypeScriptにはobjectという型を用いてオブジェクトを表現することができます。

```tsx
let obj: object = {
	data: "hoge"
}
```

しかし、これだけではほとんど意味がなく、JavaScriptのオブジェクトであることしか宣言することができません。

オブジェクトの内部に型を指定する方法としては、以下のようにします。

```tsx
let obj: {key: number} = {
	key: 1
}
```

こうすることで、オブジェクトのkeyというプロパティにnumberの型を宣言することができました。これを**オブジェクトリテラル表記**と呼びます。

また、オブジェクトリテラル表記されたオブジェクトに型の異なるプロパティを追加したり、求められているプロパティを省略した例を見てみましょう。

```tsx
let onamae: {
  firstName: string
  lastName: string
} = {
  firstName: "Yoshio"
};
// エラー

onamae = {
	nickName: "Yoshi"
};
// エラー
```

このように、予めオブジェクトに期待しているプロパティがなかったり、期待していないプロパティが代入されてしまうとTypeScriptはエラーを出します。

しかし、時にはプロパティを省略したり、追加したくなると思います。そんな時には以下のように記述します。

```tsx
let address: {
  country?: string,
  [code: number]: string
};

address = {
  1: "one"
};
// - ①

address = {
  country: "America",
  2: "two",
  3: "third"
}
// - ②

address = {
  country: "Japan"
}
// - ③
```

見慣れない表記が出てきたと思います。addressオブジェクトのcountryプロパティの右側に`?` がついていますが、これは**省略可能であること**を示しています。具体的には`undifined`を受け入れることを表し、この場合はcountryプロパティは`string`と`undifined`両方の型を受け入れます。

そのため、①の例のようにcountryプロパティが省略されてもエラーは出ません。

また、codeプロパティは`[]` に囲まれていますが、この構文は**インデックスシグネチャ**といい、オブジェクトが多くのプロパティを含む可能性を示します。インデックスシグネチャを使うことで、明示的に宣言したプロパティ以外にも多くのキーを安全にオブジェクトに追加することができます。

②のように、複数の`number`の型を持つプロパティと`string`の型を持つ値を与えることができます。

さらに、③の例のように省略することも可能です。

ただし、インデックスシグネチャには注意点があり、プロパティの型は`number`か`string`しか使えないということです。

## 配列

TypeScriptで配列に型をつける場合は以下のよう型アノテーションの後に`[]`をつけます。

```tsx
let stringArray: string[];
stringArray = ["hoge", "fuga"];
```

## タプル

タプルは配列の派生であり、配列の長さを固定することができます。以下に例を示します。

```tsx
let numStringTuple: [number, number, string] = [1, 2, "3"];

let optionalTuple: [number, number?] = [1];

let variableTuple: [string, ...string[]] = ["A", "B", "C", "D"];
```

タプルは配列宣言と似ており、違いは`[]` 内に型を記述することです。タプル内で`?` 表記を使うことで省略可能な要素を記述することもできますし、最後の例のように可変長の要素も使えます。

## any

`any`はなんでも代入可能な型です。最終手段の型であり、TypeScriptの恩恵を全く受けれなくなる(型安全性を保証できなくなる)ので、極力使わないほうがいいでしょう。

```tsx
let allOK: any;
allOK = "ok?"
allOK = 123;
allOK = true;
// 全て許容される
```

## unknown

`unknown`は`any`と同様、型がわからない時に使う場合がありますが、こちらを優先して使ってください。なぜならunknownは与えられた値の型を絞り込むまで値を使用させないようにしてくれるからです。例を見てみましょう。

```tsx
let unknownValue: unknown = 100;
let u = unknownValue + 1;
// コンパイルエラー: Object is of type 'unknown'

if (typeof unknownValue === "number") {
  let num = unknownValue + 100;
}
// unknownValueがnumberであることが保証されたため使用することができる！
```

変数unknownValue は`unknown` 型を与えらたので、その型がわからないまま使用する(この例では1を足す)と、エラーとなってしまいます。

しかし、型をnumberに絞り込んであげることで型が保証され、その値を使用することができることができます。これらからanyよりも型安全性が高く、こちらを使うことを推奨します。

## null, undefined

JavaScriptには値がないことを`null`, `undefined`を用いて表します。`null`は値が入っていないことを意味し、`undefined`はまだ定義がされていないことを意味します。TypeScriptではこれらを型として扱うことができます。

```tsx
let n: null;
let u: undefined;
```

## void, never

さらにTypeScriptには値がないことを識別するために`void` , `never` の2つの型を用意しています。`void`は明示的に何にも返さない関数の戻り値の型であり、`never`は決して返ることのない(例外をスローする場合や、無限ループなど)関数の型です。

```tsx
function logger() : void {
  console.log("何も返さないよ");
}

function errorGenerator() :never{
  throw TypeError("決して返らないよ");
}
```

## 型エイリアス

変数に名前をつけて、値を代入できるのと同様に、TypeScriptでは型に名前をつけて宣言することができます。これを**型エイリアス**(**type alias**)と呼びます。宣言方法は、`type 型名`とします。

以下に例を示します。

```tsx
type Coffee = {
  name: string,
  country: string,
  code: number,
};
```

新たに定義した、`Coffee` を利用するには、以下のようにします。

```tsx
// アラビカコーヒーを定義
const arabica: Coffee = {
  name: "Arabica",
  country: "Ethiopia",
  code: 1,
}

// Coffeeで定義されていたcodeプロパティが存在しないためエラー
const american: Coffee = {
  name: "American coffee",
  country: "America"
};
```

新たにコーヒーの種類として、型エイリアスを利用してアラビカコーヒーを定義しています。下の例ではCoffeeの型エイリアスで指定していたcodeプロパティが欠如しているためエラーとなります。

また、JavaScriptの変数と同様に、基本同じ型を2度宣言することはできません。さらに、型エイリアスはブロックスコープなため、スコープの有効範囲はブロック内に限ります。そのため、以下のように別のブロックでは同じ型を宣言することができます。

```tsx
type status = "done";
type status = "progress";
// コンパイルエラー : 2度同じ名前の型エイリアスを宣言できない

if (isStatus) {
  type status = "done";
} else {
  type status = "progress";
}
// 型エイリアスはブロックスコープなので2度同じ名前の型エイリアスを宣言したことにならない
```

## 合併型(Union Types)と交差型(Intersection Types)

これまでたくさんの型について紹介してきましたが、これらの型を組み合わせたりしてもっと複雑な型を表現したい場面があると思います。そんな時に便利なのが**合併型(Union Types)**と**交差型(Intersection types)**です。

合併型は型A, Bの和集合を表す型で、交差型は積集合を表す型です。合併型は`|`、交差型は`&`演算子を使って表現します。具体例を見ていきましょう。

```tsx
type Kick = {
  effect: boolean,
  hit: boolean,
};

type Punch = {
  hit: boolean,
  damage: number,
};

type KickOrPunch = Kick | Punch;
type KickAndPunch = Kick & Punch;
```

型エイリアス`KickOrPunch`は型KickとPunchの合併型であり、`KickAndPunch`は交差型です。ではそれぞれはどのような型を受け入れるのでしょうか。

```tsx
// 合併型なので、Kick, Punch型両方のプロパティが使える
const SuperAtack: KickOrPunch = {
  effect: true,
  hit: true,
  damage: 10,
};

// 合併型なため、どちらかの型ということを表せれば良い
const normalAtack: KickOrPunch = {
  hit: true,
  damage: 3,
};

// 交差型なので、合併型同様Kick, Punch型両方のプロパティが使える
const doubleAtack: KickAndPunch = {
  effect: true,
  hit: true,
  damage: 10,
};

// 交差型は集合元の型全てのプロパティを使わなくてはならない
const errorNormalAtack: KickAndPunch = {
  hit: true,
  damage: 3,
};
// エラー: Kick typeのeffectプロパティがないため
```

合併型である`SurperAtack` 、`normalAtack`はKick、Punchどちらかの型か両方の型であることを示せればよいので、両方の型か一方の型のプロパティを全て使うことができます。

一方交差型は集合元のプロパティを全て宣言する必要があります。

また、合併型は関数の引数などで、以下のような使い方をよくします。

```tsx
function atack(power: string | number) {
	...
}
```

これは関数の引数`power` が`string`、`number`どちらかであることを示しています。

このようにTypeScriptでは他の静的型付け言語で扱えるような型やそれ以上にさまざまな型を宣言できることがわかっていただけたと思います。

もっと詳しく学びたい方は、是非[ブログ](https://tyotto-good.com/)も見ていってください！

それでは🙌

参考:  [https://www.typescriptlang.org/docs/handbook/basic-types.html](https://www.typescriptlang.org/docs/handbook/basic-types.html)