TypeScript の魅力は、なんといっても「型」で間違いを事前に見つけられることです。なかでも関数は、どんな値を受け取り、どんな値を返すのかを型で明示できると、書き手にも読み手にも安全でわかりやすいコードになります。この記事では、引数と戻り値の型注釈の基本から、アロー関数での書き方、戻り値がないときの void、省略できるオプション引数、そして関数そのものを表す型(コールバックの型付け)まで、初心者がつまずきやすい順に丁寧に解説します。
目次
なぜ関数に型を付けるのか
関数に型を付ける最大の理由は、誤った値を渡す・受け取ることをコンパイル時に防げるからです。たとえば「2つの数値を足す関数」に、うっかり文字列を渡してしまったとします。JavaScript ではそのまま実行され、"1" + 2 のように予期しない結果("12")になってから初めて気づきます。TypeScript なら、実行する前にエディタ上で赤い波線が出て教えてくれます。
もう1つの利点は、型がドキュメントの代わりになることです。関数のシグネチャ(引数と戻り値の型)を見れば、その関数が何を必要とし何を返すのかが一目でわかります。エディタの入力補完も型の情報をもとに働くため、開発中の体験も大きく向上します。
引数と戻り値に型注釈を付ける
もっとも基本的な形は、function 宣言に型注釈を付けるものです。引数は変数名のうしろに : 型 を書き、戻り値は引数リストの閉じ括弧のあと、波括弧の前に : 型 を書きます。
// a と b は number 型、戻り値も number 型
function add(a: number, b: number): number {
return a + b;
}
const result = add(3, 5); // result は number 型(8)
add(3, "5"); // ❌ エラー: 型 'string' を 'number' に割り当てられません
各部分がどこにかかっているのかを整理すると、次のようになります。
| 記述 | 意味 |
|---|---|
a: number | 第1引数 a は数値であること |
b: number | 第2引数 b は数値であること |
): number | この関数が返す値は数値であること |
戻り値の型を書いておくと、関数の中身が意図した型を返しているかもチェックされます。たとえば return を書き忘れたり、間違えて文字列を返したりすると、その場でエラーになります。関数の「入口」と「出口」の両方を型で固めておくイメージです。
アロー関数での型注釈の書き方
アロー関数でも考え方は同じで、引数と戻り値に型を付けられます。引数リストのうしろ、=> の前に戻り値の型を書きます。
// 引数 a, b は number、戻り値も number
const add = (a: number, b: number): number => a + b;
// 波括弧を使うブロック形式でも同じ
const greet = (name: string): string => {
return `こんにちは、${name}さん`;
};
console.log(add(2, 4)); // 6
console.log(greet("田中")); // こんにちは、田中さん
ここで気をつけたいのは、const add = (...): number => ... の : number は関数の戻り値の型であって、変数 add の型ではないという点です。=> の直前に置かれた型注釈は、あくまでその関数が返す値についての指定です。変数 add 自体の型(=関数の型)については、このあとの章で扱います。
戻り値がない関数は void で表す
ログ出力やアラート表示のように、値を返さない関数もよくあります。こうした関数の戻り値の型には void を使います。void は「返すべき値がない(=呼び出し側で戻り値を使わない)」ことを表す専用の型です。
// メッセージを表示するだけで、何も返さない
function logMessage(message: string): void {
console.log(message);
}
const returnValue = logMessage("処理を開始します");
// returnValue は undefined 型になる(使う意味はない)
void を指定した関数の中で return 値; のように値を返そうとすると、エラーになります。ただし、値を伴わない return;(早期リターン)は、途中で処理を抜けるために使えるので問題ありません。「この関数は結果を返さない」という意図を型で明示できるのが void の役割です。
省略できる引数(オプション引数)とデフォルト引数
引数の中には「渡しても渡さなくてもよい」ものがあります。そうした引数は、名前のうしろに ? を付けてオプション引数にします。オプション引数は、渡されなかったときに undefined になる可能性があるため、その型は自動的に「指定した型 | undefined」として扱われます。
// title は省略可能。省略すると undefined になる
function createLabel(name: string, title?: string): string {
if (title) {
return `${title} ${name}`;
}
return name;
}
console.log(createLabel("山田")); // 山田
console.log(createLabel("山田", "課長")); // 課長 山田
title?: string は「string か、渡されなければ undefined」という意味です。そのため、関数の中でそのまま使う前に if (title) のように値があるか確認するのが安全です。確認せずに title.length などとアクセスしようとすると、TypeScript が「undefined かもしれない」と警告してくれます。
一方、省略されたときに初期値を使いたい場合は、デフォルト引数が便利です。= 値 を書いておくと、引数が渡されなかったときにその値が使われます。デフォルト値から型が推論できるので、型注釈は省略できることが多いです。
// unit を省略すると "円" が使われる。型は "円" から string と推論される
function formatPrice(amount: number, unit = "円"): string {
return `${amount}${unit}`;
}
console.log(formatPrice(1000)); // 1000円
console.log(formatPrice(10, "ドル")); // 10ドル
オプション引数(?)とデフォルト引数(= 値)は、どちらも「省略できる引数」を作る方法ですが、省略時の挙動が違います。? は省略すると undefined になり、デフォルト引数は省略すると指定した初期値になります。なお、この2つを同じ引数に同時に指定することはできません(デフォルト値がある時点で、その引数は省略可能だからです)。
関数そのものの型を定義する(コールバックの型付け)
ここまでは「引数」と「戻り値」に型を付けてきましたが、関数そのものを1つの型として表すこともできます。これは、関数を引数として受け取るとき(コールバック)や、関数を変数に代入するときに役立ちます。書き方は (引数: 型) => 戻り値の型 です。
// 「number を受け取り number を返す関数」という型
type Fn = (x: number) => number;
// Fn 型なので、引数と戻り値の型は書かなくてよい
const double: Fn = (x) => x * 2;
const square: Fn = (x) => x * x;
console.log(double(5)); // 10
console.log(square(5)); // 25
この関数型が本領を発揮するのは、コールバック関数を受け取る関数を書くときです。たとえば「数値の配列を受け取り、それぞれに何らかの変換をかける」関数を考えてみましょう。変換のしかたを表す引数を、先ほどの Fn 型で受け取れます。
type Fn = (x: number) => number;
// 第2引数 transform は「number を受け取り number を返す関数」
function mapNumbers(numbers: number[], transform: Fn): number[] {
return numbers.map(transform);
}
const nums = [1, 2, 3];
console.log(mapNumbers(nums, (x) => x * 10)); // [10, 20, 30]
コールバックの型を Fn として渡しているので、mapNumbers に渡す関数 (x) => x * 10 の x は自動的に number と推論されます。呼び出し側でいちいち型を書かなくてよくなり、しかも間違った形の関数を渡すとエラーで教えてくれます。
戻り値の型は省略できることが多い
TypeScript には強力な型推論があり、戻り値の型は書かなくても関数の中身から自動的に判断してくれます。たとえば a + b(どちらも number)を返す関数なら、戻り値は書かなくても number と推論されます。
// 戻り値の型を書かなくても、number と推論される
function add(a: number, b: number) {
return a + b;
}
// 上は次のように書いたのと同じ扱いになる
function addExplicit(a: number, b: number): number {
return a + b;
}
では、戻り値の型はいつ書けばよいのでしょうか。基本方針としては、引数の型は書く/戻り値の型は推論に任せるのがバランスの良い出発点です。引数は推論の起点になる情報なので明示し、戻り値は中身から決まるので省略する、という考え方です。
ただし、次のような場合はあえて戻り値の型を書くと安全です。第一に、公開する関数(他の人が使う関数やライブラリの入口)では、戻り値の型を明示すると意図しない型変更に気づけるようになります。第二に、条件分岐が複雑で「本当にこの型を返しているか」を保証したいときにも、書いておくとチェックが働きます。省略できるからといって、常に省略するのが最善とは限りません。
型注釈でつまずきやすいところ
オプション引数は必須引数より前に置けない
オプション引数(? が付いた引数)は、必須の引数より前に置くことができません。もし前に置くと「必須の引数が省略可能な引数のあとに来ています」というエラーになります。これは、前の引数を省略して後ろだけ渡す、という呼び出しが成立しないためです。省略できる引数は、必ず引数リストの後ろにまとめて並べます。
// ❌ 省略可能な引数を先に書くとエラー
function bad(title?: string, name: string) {
return `${title} ${name}`;
}
// ✅ 必須の引数を先に、省略可能な引数を後ろに
function good(name: string, title?: string) {
return title ? `${title} ${name}` : name;
}
オプション引数を確認せずに使ってしまう
オプション引数は undefined になりうるため、値があるか確認せずにプロパティやメソッドへアクセスすると、「オブジェクトは ‘undefined’ である可能性があります」というエラーが出ます。if (引数) でチェックするか、引数?.length のようにオプショナルチェーン(?.)を使って、値がないケースを安全に扱いましょう。エラーは不便に感じるかもしれませんが、実行時の「undefined のプロパティは読めません」という事故を未然に防いでくれているのです。
void の関数から値を返そうとする
戻り値の型を void にした関数の中で return 何らかの値; と書くと、エラーになります。もしその関数で値を返したいなら、void ではなく実際に返す型(number や string など)に直す必要があります。逆に、値を返す予定がないのに戻り値を使ってしまっているなら、呼び出し側の設計を見直すサインです。void は「返さない」という約束なので、その約束と中身を一致させましょう。
まとめ
関数の型注釈は、引数に 名前: 型、戻り値に ): 型 を付けるのが基本です。アロー関数でも同じ考え方で、=> の前に戻り値の型を書きます。値を返さない関数は void、省略できる引数は ?(オプション引数)や = 値(デフォルト引数)で表現します。関数そのものは type Fn = (x: number) => number のように型として定義でき、コールバックの型付けで力を発揮します。戻り値の型は推論に任せて省略できる場面が多い一方、公開する関数では明示すると安全です。まずは「引数の型は書き、戻り値は推論に任せる」ところから始め、必要に応じて明示を足していくとよいでしょう。