型付けから理解するTypeScript

# はじめに
筆者はTypeScript初学者です。学習する中で、「型 'XXX' を型 'YYY' に割り当てることはできません。」というようなエラー文と対峙する機会が多いと思いました。これはTypeScriptの型付けシステムを理解することで、なんとなく解消していたエラー文への理解が深まったり、腑に落ちることがありました。初学者が最初に知っておくと今後の学習や開発がスムーズに進むのではないかと思い、TypeScriptの型付けシステムについて学んだことをこの記事にまとめようと思います。

ここでは、スーパータイプ、サブタイプの考え方から理解を深めていこうと思います。

# TypeScriptの型付けアプローチ
多くのプログラミング言語では、型と型の関係性は階層関係で捉えることができます。階層構造において、上位に位置する型を<EMPTY>**基本型(supertype)**<EMPTY/>と言い、階層構造の下位に位置する型を<EMPTY>**部分型(subtype)**<EMPTY/>と呼びます。
**「型 'XXX' が型 'YYY' に割り当てられる」ということは「型'YYY'は型'XXX'のサブタイプである」ことを指します。**

TypeScriptでは**構造的部分型**と呼ばれるアプローチをとっています。TypeScript以外の言語では、公称型と呼ばれる、**型の名前に基づいて**型区別し、型同士の関係を決めるようなアプローチをとっている言語が多くありますが、構造的部分型の特徴は、**型の構造に基づいて**型区別し、型同士の関係を決めていることです。

## 構造的部分型
JavaScriptの型付けアプローチは構造的型付けであり、その特徴から**ダックタイピング**とも呼ばれています。

ダックタイピングでは、オブジェクトのプロパティやメソッドがどのようなものか、つまりオブジェクトの構造をみてオブジェクトの型を判断します。

:::note info
ダックタイピングは以下の有名な一文に由来しているようです。
>"If it walks like a duck and quacks like a duck, it must be a duck"
（もしもそれがアヒルのように歩き、アヒルのように鳴くのなら、それはアヒルに違いない）

引用：[ダック・タイピング - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%80%E3%83%83%E3%82%AF%E3%83%BB%E3%82%BF%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0)
:::

TypeScriptはJavaScriptを拡張した言語であり、ダックタイピングに適した方式である構造的部分型を採用しています。構造的部分型も同じようにオブジェクトの構造をみて型を推測し、割り当て可能かを判断します。

以下の例では、`whiskey`はTypeScriptに`{name: string;　percentage: number;　category: string;}`と推論されます。`Alcohol`型には`category`プロパティは存在していませんが、エラーは出ず問題なくコンパイルできます。これは、`whiskey`の型が`Alcohol`型のサブタイプであるからです。TypeScriptでは、ある型'XXX'の最小限の条件満たしている型'YYY'のオブジェクトには、型'XXX'を割り当てることができます。

```typescript
interface Alcohol {
  name: string;
  percentage: number;
}

const whiskey = { // whiskey: {name: string;　percentage: number;　category: string;}と推論
  name: "hakusyu",
  percentage: 40,
  category: "distilled"
};

const getName = (drink: Alcohol) => drink.name;
getName(whiskey);  // whiskeyにAlcohol型が割り当てられ、エラーはない
```
:::note warn
上記の例では、説明のために「余剰プロパティチェック」を避けるため、以下例のようにオブジェクトリテラルを定義している値に対して直接型注釈をつけていません。TypeScriptには構造的部分型の考えとは別で、型に存在しないプロパティを禁止するチェック機能があります。

```typescript
const whiskey: Alcohol = {
  name: "hakusyu",
  percentage: 40,
  category: "distilled", // エラー：「オブジェクト リテラルは既知のプロパティのみ指定できます。'category' は型 'Alcohol' に存在しません。」
};
```
:::

https://typescriptbook.jp/reference/values-types-variables/object/excess-property-checking

反対に、以下のように`Alcohol`型がサブタイプとなってしまう場合は、コンパイルエラーとなり、型を割り当てることができません。
```typescript
interface Alcohol {
  name: string;
  percentage: number;
  category: string;
}

const whiskey = {
  name: "hakusyu",
  percentage: 40,
};

const getName = (drink: Alcohol) => drink.name;
getName(whiskey); //　エラー：「型 '{ name: string; percentage: number; }' の引数を型 'Alcohol' のパラメーターに割り当てることはできません。...」
```

# TypeScriptの型階層構造
TypeScriptにはさまざまな型が用意されていますが、それぞれの型の関係性も定義されています。以下の図では、`unknown`側が上位の型、つまりスーパータイプで、`never`側が下位の型、つまりサブタイプになります。

![typescriptの部分型関係イメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3620233/15966058-bd59-51c2-7383-c09491675dc1.png)
*※図は以下記事から引用させていただきました。*

https://zenn.dev/estra/articles/typescript-type-set-hierarchy#%E5%AE%A3%E4%BC%9D-(%E6%9C%AC%E3%82%92%E4%BD%9C%E6%88%90%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F)
## unknown型とnever型
unknown型は最上位の型で、すべての型のスーパータイプになり、**どの型も割り当てることができます**。集合に当てはめると全体集合になるイメージです。対してnever型は、最下位の型となり**どの型も割り当てることができません**。集合で考えると空集合のようなイメージです。以下は検証例です。

```typescript
// unknown型
let alcohol: unknown;

alcohol = "whiskey";
alcohol = 1992;
alcohol = true;
alcohol = { alcoholName: "whiskey" };
alcohol = null;
alcohol = undefined;

// never型
let softDrink: never;

softDrink = "whiskey"; // 型 'string' を型 'never' に割り当てることはできません。
softDrink = 1992; // 型 'number' を型 'never' に割り当てることはできません。
softDrink = true; // 型 'boolean' を型 'never' に割り当てることはできません。
softDrink = { alcoholName: "whiskey" }; // 型 '{ alcoholName: string; }' を型 'never' に割り当てることはできません。
softDrink = null; // 型 'null' を型 'never' に割り当てることはできません。
softDrink = undefined; // 型 'undefined' を型 'never' に割り当てることはできません。
```

## any型
any型は特殊な型であり、どんな型の代入も許可する型です。これはany型を代入した変数については、コンパイラーが型チェックを行わないためです。型の階層構造からは外れた型と考えられます。

https://typescriptbook.jp/reference/values-types-variables/any

### unknown型とany型
ある状況下ではunknown型とany型は同じように振る舞います。しかし、unknown型を割り当てておくと、どんな型が入ってくるかわからない状態で、ある特定の型にしか使えないメソッドを呼び出したりすると、TypeScriptはエラーを出してくれます。
ここで、any型とするとそもそも型チェックをしないので、エラーは出ずスルーされます。unknown型が使える場合は、こちらを使用する方が安全でしょう。

```typescript
const alcoholAge = "1992年";

const removeUnit = (value: unknown) => {
  return value.replace("年", ""); // valueにエラー：「'value''は 'unknown' 型です」
};

removeUnit(alcoholAge);
```
上記のエラーは型ガードを記述することで解消されます。

```typescript
const removeUnit = (value: unknown) => {
  if (typeof value !== "string") return;
  return value.replace("年", ""); // エラーなし
};
```

## プリミティブ型とリテラル型
TypeScriptは、以下の例のように`const`で宣言すると、値の型は`"whiskey"`という文字列のリテラル値と推論されています。階層構造の図で言えば、`string literal`にあたります。この型のスーパータイプは`string`（プリミティブ）です。
```typescript
const alcoholName = "whiskey"; // const alcoholName: "whiskey"と推論
```

このように、リテラル値を型にすることが可能で、ユニオン型を使用すれば、自由にスーパータイプをつくることができます。以下例の各型の関係は、`string > "whiskey" | "vodka" > "whiskey"`となります。`vodka`変数の例ではスーパータイプの型を持つ`alcoholName`変数をサブタイプの型を持つ`vodka`変数に割り当てようとしているのでエラーになります。

```typescript
const whiskey = "whiskey";

// whiskeyのスーパータイプになるため、割り当て可能
const alcoholName: "whiskey" | "vodka" = whiskey;
const drinkName: string = alcoholName;

// whiskeyのサブタイプになるため、割り当て不可
const vodka: "vodka" = alcoholName; // vodkaにエラー：「型 '"whiskey"' を型 '"vodka"' に割り当てることはできません」
```

## オブジェクトについて考えてみる
ここで、改めてオブジェクトのサブタイプを考えてみます。以下の例の`alcohol2`オブジェクトではエラーなく無事コンパイルできるでしょうか。

```typescript
const alcohol = {
  name: "whiskey",
  percentage: 40,
};

const alcohol2: { name: string; percentage: number; category: string } = alcohol;
```

答えは、エラーが出てコンパイルできません。上記例は、少し書き方は変えていますが、[構造的部分型の項](https://qiita.com/drafts/8a603c9307f619580f93/edit#%E6%A7%8B%E9%80%A0%E7%9A%84%E9%83%A8%E5%88%86%E5%9E%8B)で最初に記述しているコード例の構造と全く一緒です。`alcohol2`の型は`alcohol`の型のサブタイプとなるで、ここでは型を割り当てることができません。

変数のユニオン型の例などをみると、要素の多い方がスーパータイプというイメージがつきがちですが、オブジェクトに関してプロパティの記述は**構造の制限を厳しくすることになります**。したがって、プロパティが多いほど、制限が厳しくなりサブタイプになります。

### おまけ

以下の例は、正しくコンパイルされるでしょうか。


```typescript
const alcohol = {
  name: "whiskey",
  percentage: 40,
};

const alcohol2: { name: string; percentage: number | string;} = alcohol;
```
こちらは問題なくコンパイルされます。プロパティの値の型については、入る型の種類が多いほど、制限は緩くなるため、`alcohol2`の型は　`alcohol`の型のスーパータイプとなり型の割り当てが可能です。

# さいごに
TypeScriptの型付けアプローチを知ることで、TypeScriptへの理解が深まったのではないかと思います。関数のサブタイプを考えるともう少し難しかったり、TypeScriptの型の階層構造についても理解できていないところが多々あり、まだまだ学習が必要ではありますが、初めの一歩の内容として初学者にとって有用なものになっていれば嬉しいです。