TypeScriptの型推論(type inference)を理解する

# はじめに

TypeScript には型推論という機能があり、すべての変数/引数において型アノテーションを書く必要がない。
うまく使えばコード量を大幅に減らし、コードの可読性も高めることができる。

## 型推論とは

https://ja.wikipedia.org/wiki/%E5%9E%8B%E6%8E%A8%E8%AB%96

> 型推論（かたすいろん、英: type inference）とはプログラミング言語の機能の 1 つで、静的な型付けを持つ言語において、変数や関数シグネチャの型を明示的に宣言しなくても、初期化のための代入式の右辺値や関数呼び出し時の実引数などといった、周辺情報および文脈などから自動的に（暗黙的に）各々の型を決定する機構のこと。

変数/引数に対して明示的に型を宣言しなくても、周辺の文脈から型を暗黙的に決定することができる仕組みのことを指す。
以下が一番シンプルな型推論の例である。

```ts
let x = 10;
// 本来ならlet x: number = 10;
```

この例では`x`という変数に対して`10`を代入している。
あえて宣言をしなくても代入する値が数値型であることから、コンパイラは`x`は`number`であると判断する。
例えばこの変数に、文字列型など別の型を代入しようとするとコンパイルエラーとなる。

## なぜ型推論を使うのか

型推論を使わずにすべての変数/引数に型アノテーションを書くことは可能である。
しかし型推論を使うことで、冗長的に型を宣言する必要がなくなるので、ソースの可読性が上がる。
また不必要に型を書くことがないので、全体のコード量が減り開発効率アップにも繋がる。
特に大きなアプリケーションになればなるほど、利用する型も複雑になるので逐一型を書くのは非効率である。

# 解説

## IDE での確認方法

VSCode などの IDE を利用していれば、どのように型推論がなされているかを確認できる。
型を確認したい変数にマウスオーバーすることで型が表示される。
今回の例だと変数`x`は `number` 型であることが分かる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272803/18f25c13-8b8e-1d86-ff88-9f6a9a08616e.png)

型が`any`や`unknown`となっている場合は、型推論がうまくできていない可能性があるので要チェック。
特に`any`の場合は tsconfig の設定次第ではエラーが出ないのでバグの原因になりやすい。

## 一番ベーシックな型推論

先ほども紹介した最もシンプルな型推論。
変数宣言時に代入する右辺の型から宣言した変数の型を決定することができる。
本来なら`let x: number = 10;`と書くところを、型アノテーションを省略できる。

```ts
let x = 10;

x = "aaa";
// xはnumber型なのでエラー
```

もちろん数値型でなくても動く。

```ts
let success = false;
// success: boolean
let name = "hoge";
// name: string
```

右辺が固定値でなくても、変数/関数の場合も同様である。

```ts
let str = "aaa";
// str: string

let a = str;
// a: string

function makeNumber(): number {
    return 10;
}

let b = makeNumber();
// b: number
```

推論された型が、期待している型と違っているケースも存在する。
このようなケースでは明示的に型を書く必要があるので注意が必要。

以下の例では、期待している型は`[number, number]`だが、この場合は`number[]`となる。

```ts
let position = [0, 0];
// number[]
```

よくある型宣言がマストなケースも挙げておく。

右辺が空の配列（`[]`）の場合、型は`any[]`になってしまうので明示的に型を宣言する必要がある。
数値型の配列であれば、次のように書く。

```ts
let numberArray: number[] = [];
```

初期状態を`null`にしたい場合、`let x = null`だと`x`は`null`型と判断されてしまうため、
`number | null`といった形で型宣言する必要がある。

```ts
let numberOrNull: number | null = null;
```

## 関数戻り値の型推論

関数の戻り値も関数内の文脈から型推論してくれる。
ちなみに関数の戻り値の型を宣言するときは以下のように書く。

```ts
function 関数名(): 戻り値型 {
    ...処理
}
```

まずはシンプルな例から紹介する。
以下の関数`getX`は戻り値の型を書かなくても、`return 10`から戻り値が`number`であることが推論される。

```ts
function getX() {
    return 10;
}
// function getX(): number
```

戻り値の型推論は固定値だけでなく、引数の型なども考慮してくれる。
以下の例では引数の`x`, `y`が`number`であり、二つを足しているだけなので戻り値も`number`であることが分かる。

```ts
function addition(x: number, y: number) {
    return x + y;
}
// function addition(x: number, y: number): number
```

if 文による分岐などに応じて Union 型を返してくれる。
以下の例では`x`の値に応じて`string`もしくは`number`を返すので戻り値の型は`string | number`となる。

```ts
function numberOrString(x: number) {
    if (x < 10) {
        return String(x);
    }

    return x;
}
// function numberOrString(x: number): string | number
```

複雑な関数を書くときは、戻り値を型を担保する意味であえて型推論をあえて使わずに型を書くことが多い。
静的型チェックを通すことでバグを未然に防ぐことができる。

```ts
function complexFunc(x: number): string {
    if (x < 0) {
        return false;
        // 戻り値はstringなので、エラー
    }

    return "hogehoge";
}
```

## 左辺からの型推論

左辺で宣言した<b>関数の型</b>から右辺の関数の<b>引数・戻り値</b>を推論するというケースもある。
少し複雑なので順を追って説明をしていく。

まずは関数の型`Addition`を宣言する。
以下から分かるように関数の型`Addition`は引数`x`、`y`と戻り値が`number`である。

```typescript
type Addition = (x: number, y: number) => number;
```

左辺に`add`という変数を置き、先ほど作った`Addition`という型で宣言する。
右辺で無名関数を代入するとき、無名関数の引数の型は`Addition`から推論できるので宣言の必要がない。
ちなみに戻り値についても推論されるので、`number`以外を返すとエラーになる。

```typescript
const add: Addition = (x, y) => {
    return x + y;
};
```

この形は React のコンポーネントで使うことが多い。
以下が普通に型推論を使わずに書いたケース。

```tsx
const Component = (props: { x: number }): JSX.Element => {
    const { x } = props;
    return <div>{props.x}</div>;
};
```

左辺からの型推論を使うとこんな風に書くこともできる。
`React.FC`は React が用意した関数コンポーネントの型である。
ジェネリクスで書くことができ、<>の中に Props の型を渡すことできる。
左辺でコンポーネントのアノテーションを先にすることで、右辺の`x`の型が推論されている。

```tsx
const Component: React.FC<{ x: number }> = ({ x }) => {
    return <div>{x}</div>;
};
```

さらに応用編で、以下の例でも型推論が機能する。
`execAdd`の第一引数は`Addition`型であることから、この関数を利用する際に`x`と`y`については型宣言の必要がない。
同様に戻り値が`number`だと言うことも推論されるので`number`以外を返すとエラーになる。

```ts
function execAdd(add: Addition) {
    return add(1, 2);
}

// ここでx, yについて型アノテーションの必要がない
execAdd((x, y) => {
    // number以外を返すとエラーになる
    return x + y;
});
```

この形は、React の高階 (Higher-Order) コンポーネントにもよく利用される。
`wrapComponent`という関数は第一引数に`Props`が`x: number`の React のコンポーネント型を受け取ることが分かるので、
この関数を利用するときに、`Props`の`x`は数値型であることが推論される。

```tsx
const wrapComponent = (
    Component: React.ComponentType<{
        // ここでxを型を書く
        x: number;
    }>
) => {
    const WrappedComponent: React.FC<{}> = () => {
        return <div>{Component}</div>;
    };
    return WrappedComponent;
};

// ここでxのアノテーションが必要ない
const Wrapped = wrapComponent(({ x }) => {
    return <div></div>;
});
```

ジェネリクスの知識も必要になるが、ここまで理解できれば型推論をうまく活用できるはず。

# おわりに

今回は TypeScript の型推論について、実際の使われ方なども踏まえて説明を行った。
型推論は他人が書いたソースを読む上でも重要なので、困ったら是非見返してほしい。

次回はジェネリクスの解説に挑戦したい。