関数型は関数の引数と戻り値を表す型で、アローを使って (a: number, b: number) => number のように書きます。type で名前を付ければ、変数、引数、オブジェクトのプロパティに使えます。
add に代入した関数には型注釈が要りません。引数の型は Operation から決まります。これを文脈による型付け(contextual typing)といい、関数型に名前を付ける一番の理由です。
関数型の構文
形は (parameters) => ReturnType です。各引数には名前と型が必要です。名前は読みやすさとエディターのヒントのためのもので、引数の型が互換であれば、引数名が何であっても一致します。
type Predicate = (value: number) => boolean;
type Formatter = (value: number, digits?: number) => string; // optional parameter
type Logger = (...parts: string[]) => void; // rest parameter
type Factory = () => { id: number }; // no parameters
名前は省略できません。(string) => void と書くと、型のない string という名前の引数を宣言したことになり、strict モードでは TS7051: Parameter has a name but no type. Did you mean 'arg0: string'? で拒否されます。(value: string) => void と書きましょう。
アロー関数に型を付ける
アロー関数の型付けには2通りあります。関数そのものに注釈を書くか、変数に関数型を付けて引数の型を推論させるかです。
独立した関数なら1の形が一般的です。3の形は、複数の関数が同じシグネチャを共有するとき(ハンドラー、比較関数、変換関数)に効果を発揮します。シグネチャを1回書けば済み、変更したときはすべての箇所がチェックされます。
コールバックの型
関数型を型に持つ引数がコールバックです。呼び出し側が渡す関数はその型と照らし合わせてチェックされ、関数の引数の型もそこから推論されます。
コールバックを使いやすくしているルールが2つあります:
- 引数が少ないのは問題ありません。 引数を1つだけ取る関数を、2つ渡される場所に渡せます。JavaScriptは余分な引数を無視するので、TypeScriptも許可します。型が渡すよりも多くの引数を取る関数はエラーです(
Target signature provides too few arguments.)。 voidの戻り値は何でも受け付けます。(...) => voidと型付けされたコールバックは値を返してもかまいません。呼び出し側はそれを使わないと約束しています。pushは数値を返すのにlist.forEach((x) => other.push(x))がコンパイルできるのはこのためです。
ただし引数の型は互換である必要があります。(a: number, b: number) => number が期待される場所に (x: string) => ... を渡すと、Types of parameters 'x' and 'a' are incompatible. で失敗します。
呼び出しシグネチャ
(n: number) => string は、呼び出しシグネチャを持つオブジェクト型 { (n: number): string } の省略形です。関数がプロパティも持つ場合は長い形が必要です。
TypeScriptでは、同じスコープで const で宣言した関数にプロパティを追加でき、そのプロパティは関数の型に反映されます。インターフェースにも呼び出しシグネチャを書けます: interface Counter { (): number; count: number }。
コンストラクトシグネチャは new で呼び出すものを表します。前に new を付けて、new (name: string) => User、オブジェクトの形なら { new (name: string): User } と書きます。クラスを受け取る引数にはこの方法で型を付けます。
メソッド構文とプロパティ構文
オブジェクト型の中では、関数のメンバーをメソッドとして handle(value: string): void と書くことも、関数を持つプロパティとして handle: (value: string) => void と書くこともできます。同じに見えますが、strict のもとではチェックのされ方が違います:
コンパイラーは次のエラーを報告します:
index.ts(8,27): error TS2322: Type '(value: string) => void' is not assignable to type '(value: string | number) => void'.
Types of parameters 'value' and 'value' are incompatible.
Type 'string | number' is not assignable to type 'string'.
Type 'number' is not assignable to type 'string'.
このエラーは正しい指摘です。onlyStrings は数値を扱えません。メソッドの形では同じ間違いが通ってしまい(歴史的な理由でメソッドの引数は「双変」でチェックされます)、a.handle(42) は実行時に TypeError: value.toUpperCase is not a function でクラッシュします。b の行を消すとそのクラッシュを確認できます。自分でオブジェクト型を書くなら、プロパティの形のほうが多くの間違いを捕まえられます。
Function 型は避ける
Function は、あらゆる関数値が満たす組み込みの型です。これが望ましいことはほとんどありません。TypeScriptは引数も戻り値の型もわからないので、どんな呼び出しも受け付け、結果は any になります。
Function は本当のシグネチャに置き換えましょう。本当にどんな関数でも受け付けたい場合(汎用の debounce やログ用のラッパー)、(...args: never[]) => unknown ならあらゆる関数を受け付けつつ、結果を any ではなく unknown に保てます。入力によって型が決まる関数については ジェネリクス を参照してください。ジェネリックな関数型は <T>(value: T) => T のようになります。
よくある質問
TypeScriptで関数型を定義するには?
アロー構文を使います: (a: number, b: number) => number。型エイリアスで名前を付け(type Compare = (a: number, b: number) => number;)、その名前を変数、引数、プロパティに使います。引数名は構文の一部ですが、ドキュメントのためだけのものです。引数名が違う関数でも型は一致します。
TypeScriptでコールバック引数に型を付けるには?
関数型を引数の型として書きます: function onEach(items: string[], cb: (item: string, index: number) => void) { ... }。呼び出し側は引数の少ない関数を渡すこともでき、コールバック自身の引数の型はその型から推論されるので、onEach(list, (item) => ...) には型注釈が要りません。
TypeScriptで Function 型を使うべきでないのはなぜですか?
Function はどんな関数でも受け付け、呼び出しがチェックされません。どんな引数でも渡せて、結果は any になります。代わりに () => void や (value: string) => number のように本当のシグネチャを書きましょう。本当にあらゆる関数を受け付けたいなら (...args: never[]) => unknown を使います。
関数型と呼び出しシグネチャの違いは何ですか?
同じものを表しています。(n: number) => string はオブジェクト型 { (n: number): string } の省略形です。関数がプロパティも持つ場合は、{ (n: number): string; label: string } のように呼び出しシグネチャの形が必要です。
TypeScriptでアロー関数に型を付けるには?
引数と戻り値の型をその場で書く方法(const half = (n: number): number => n / 2;)と、変数に関数型を付けて引数の型を推論させる方法(const half: (n: number) => number = (n) => n / 2;)のどちらかです。