LoginSignup
31
26

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-05-25

はじめに

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

型推論とは

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

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

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

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

なぜ型推論を使うのか

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

解説

IDE での確認方法

VSCode などの IDE を利用していれば、どのように型推論がなされているかを確認できる。
型を確認したい変数にマウスオーバーすることで型が表示される。
今回の例だと変数xnumber 型であることが分かる。
image.png

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

一番ベーシックな型推論

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

let x = 10;

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

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

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

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

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

let a = str;
// a: string

function makeNumber(): number {
    return 10;
}

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

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

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

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

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

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

let numberArray: number[] = [];

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

let numberOrNull: number | null = null;

関数戻り値の型推論

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

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

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

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

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

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

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

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

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

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

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

    return "hogehoge";
}

左辺からの型推論

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

31
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
26