はじめに
TypeScript には型推論という機能があり、すべての変数/引数において型アノテーションを書く必要がない。
うまく使えばコード量を大幅に減らし、コードの可読性も高めることができる。
型推論とは
型推論(かたすいろん、英: type inference)とはプログラミング言語の機能の 1 つで、静的な型付けを持つ言語において、変数や関数シグネチャの型を明示的に宣言しなくても、初期化のための代入式の右辺値や関数呼び出し時の実引数などといった、周辺情報および文脈などから自動的に(暗黙的に)各々の型を決定する機構のこと。
変数/引数に対して明示的に型を宣言しなくても、周辺の文脈から型を暗黙的に決定することができる仕組みのことを指す。
以下が一番シンプルな型推論の例である。
let x = 10;
// 本来ならlet x: number = 10;
この例ではx
という変数に対して10
を代入している。
あえて宣言をしなくても代入する値が数値型であることから、コンパイラはx
はnumber
であると判断する。
例えばこの変数に、文字列型など別の型を代入しようとするとコンパイルエラーとなる。
なぜ型推論を使うのか
型推論を使わずにすべての変数/引数に型アノテーションを書くことは可能である。
しかし型推論を使うことで、冗長的に型を宣言する必要がなくなるので、ソースの可読性が上がる。
また不必要に型を書くことがないので、全体のコード量が減り開発効率アップにも繋がる。
特に大きなアプリケーションになればなるほど、利用する型も複雑になるので逐一型を書くのは非効率である。
解説
IDE での確認方法
VSCode などの IDE を利用していれば、どのように型推論がなされているかを確認できる。
型を確認したい変数にマウスオーバーすることで型が表示される。
今回の例だと変数x
は number
型であることが分かる。
型がany
やunknown
となっている場合は、型推論がうまくできていない可能性があるので要チェック。
特に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
だとx
はnull
型と判断されてしまうため、
number | null
といった形で型宣言する必要がある。
let numberOrNull: number | null = null;
関数戻り値の型推論
関数の戻り値も関数内の文脈から型推論してくれる。
ちなみに関数の戻り値の型を宣言するときは以下のように書く。
function 関数名(): 戻り値型 {
...処理
}
まずはシンプルな例から紹介する。
以下の関数getX
は戻り値の型を書かなくても、return 10
から戻り値がnumber
であることが推論される。
function getX() {
return 10;
}
// function getX(): number
戻り値の型推論は固定値だけでなく、引数の型なども考慮してくれる。
以下の例では引数のx
, y
がnumber
であり、二つを足しているだけなので戻り値も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
は引数x
、y
と戻り値が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
型であることから、この関数を利用する際にx
とy
については型宣言の必要がない。
同様に戻り値がnumber
だと言うことも推論されるのでnumber
以外を返すとエラーになる。
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
は数値型であることが推論される。
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 の型推論について、実際の使われ方なども踏まえて説明を行った。
型推論は他人が書いたソースを読む上でも重要なので、困ったら是非見返してほしい。
次回はジェネリクスの解説に挑戦したい。