関数と型 — 引数・戻り値・オーバーロード
この章の目次開く
この章では、関数に関する型付けパターンを整理します。02章で触れた基本に加え、実務で頻出するパターンを扱います。
学習者関数の引数と戻り値、どこまで型を書けばいいんだろう?全部書くべき?それとも省略できる?
関数の型の書き方(基本)
関数の型は「引数」と「戻り値」の2箇所に付けます。
function add(a: number, b: number): number {
return a + b;
}このうち引数の型は必須です。書かないと any と推論され、strict モードでは Parameter 'a' implicitly has an 'any' type というエラーになります。
一方、戻り値の型は return 文から推論されるため省略できます。それでも明示をおすすめするのは、次の2つの理由からです。
- 実装を間違えたとき、関数の内側でエラーが出る(省略すると間違った型が呼び出し側へ伝播し、離れた場所でエラーになる)
- 関数のシグネチャだけで「何を受け取り何を返すか」が読める、ドキュメントとして機能する
アロー関数でも書き方は同じです。
const multiply = (a: number, b: number): number => a * b;関数の型そのものを定義する
「関数を受け取る・返す」場面では、関数の型そのものを書く必要があります。
構文: (引数名: 型) => 戻り値の型
// 型エイリアスで関数の型に名前を付ける
type Comparator<T> = (a: T, b: T) => number;
const byAge: Comparator<{ age: number }> = (a, b) => a.age - b.age;変数側に関数の型が付いていれば、実装側の引数の型は省略できます(文脈から推論される)。上の byAge で a と b に型を書いていないのはこのためです。なお、型定義側の引数名(a, b)は説明用のラベルにすぎず、実装側と一致している必要はありません。
オプショナル引数とデフォルト値
「省略できる引数」の書き方は2種類あります。
構文: function fn(必須: 型, 任意?: 型, 既定値あり: 型 = 値)
まず ? を付けるオプショナル引数です。
function greet(name: string, greeting?: string): string {
// greeting の型は string | undefined になる
return `${greeting ?? 'こんにちは'}、${name}さん`;
}
greet('田中'); // "こんにちは、田中さん" — 省略できる
greet('田中', 'おはよう'); // "おはよう、田中さん"? を付けた引数は、関数の内部では string | undefined として扱われます。そのまま使うとエラーになるため、?? や if で undefined の処理が必要です。
次にデフォルト値付き引数です。
function greetWithDefault(name: string, greeting: string = 'こんにちは'): string {
// greeting の型は string(undefined の可能性が消えている)
return `${greeting}、${name}さん`;
}省略された場合はデフォルト値で埋められるため、関数の内部では undefined を考えなくてよくなります。2つの使い分けは次の通りです。
| 書き方 | 関数内部での型 | 使いどころ |
|---|---|---|
greeting?: string | string | undefined | 「渡されなかった」こと自体をロジックで区別したい |
greeting: string = '既定値' | string | 省略時の値が決まっている。内部の分岐が減るので、迷ったらこちら |
レストパラメータ
個数が決まっていない引数は、レストパラメータでまとめて受け取ります。
構文: function fn(...引数名: 型[])
function sum(...numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
sum(); // 0 — 0個でもOKルールは2つだけです。引数リストの最後に1つだけ書けること、型は**配列型(またはタプル型)**にすることです。通常の引数との組み合わせもできます。
function joinWith(separator: string, ...parts: string[]): string {
return parts.join(separator);
}
joinWith('-', '2026', '07', '09'); // "2026-07-09"タプル型と組み合わせると、「最低1個は必要」のような制約も型で表現できます。
function first(...items: [string, ...string[]]): string {
return items[0]; // 少なくとも1個あることが型で保証されている
}
first('a', 'b'); // ✅
// first(); // ❌ エラー:引数が足りないコールバック関数の型
関数を引数として受け取る場合、その引数の型には前述の関数型((引数) => 戻り値)を使います。
type ClickHandler = (event: MouseEvent) => void;
function registerHandler(handler: ClickHandler): void {
document.addEventListener('click', handler);
}
// 呼び出し側は引数の型を書かなくてよい(event は MouseEvent と推論される)
registerHandler((event) => {
console.log(event.clientX);
});コールバックは「引数が少ない実装」でもよい
TypeScriptには「型定義より少ない引数で実装した関数は代入できる」というルールがあります。配列メソッドでいつも体験している挙動です。
const numbers = [1, 2, 3];
// forEach のコールバック型は (value, index, array) => void だが…
numbers.forEach((value) => console.log(value)); // ✅ 使わない引数は省略できるコールバックの実装側は、型定義にある引数を後ろから自由に省略できます。JavaScriptでは関数に余分な引数を渡しても単に無視されるだけなので、それを型システムでも許容している、という理屈です。
Reactのpropsでコールバックを渡すときも、書き方は同じです。
type ButtonProps = {
label: string;
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
};React.MouseEvent などReact固有のイベント型は13章のReactとTypeScriptで扱います。
学習者コールバック関数の型を書くとき、Function 型を使っちゃダメなの?
先生Function 型は any と同じくらい危険。引数も戻り値も何でも受け入れてしまう。(arg: string) => void のように具体的に書くのが鉄則だよ。
// ❌ Function 型は引数のチェックが一切効かない
const handler: Function = (a: number) => a * 2;
handler('文字列', true, []); // エラーにならず、実行時に壊れる関数オーバーロード
学習者「引数の型によって戻り値の型が変わる関数」って、ユニオン型じゃ書けないの…?
ユニオン型でも書けますが、戻り値の型の対応関係が失われます。
// ユニオン型で書くと…
function double(value: string | number): string | number {
return typeof value === 'string' ? value + value : value * 2;
}
const result = double(5);
// result の型は string | number — number を渡したのに絞り込まれないnumber を渡したら戻り値も number のはずですが、呼び出し側ではそれが分かりません。この「引数と戻り値の対応」を表現できるのが関数オーバーロードです。
構文: シグネチャ(型だけの宣言)を複数並べ、最後に実装を1つ書く
// オーバーロードシグネチャ(呼び出し側から見える型)
function double(value: string): string;
function double(value: number): number;
// 実装シグネチャ(すべてのパターンを受けられる型で書く。外からは見えない)
function double(value: string | number): string | number {
return typeof value === 'string' ? value + value : value * 2;
}
const s = double('abc'); // string 型 — "abcabc"
const n = double(5); // number 型 — 10とはいえ、オーバーロードは「読み手が複数のシグネチャを突き合わせる」コストがかかります。TypeScript公式ドキュメントも「可能ならオーバーロードではなくユニオン型の引数を使う」ことを推奨しています。使い分けの目安は次の通りです。
| 状況 | 選ぶ手段 |
|---|---|
| 引数の型が違うだけで、戻り値の型は変わらない | ユニオン型で十分 |
引数の型と戻り値の型が連動する(T を受けて T を返す等) | ジェネリクスが第一候補 |
| 引数の型ごとに戻り値の型が別々に決まる/引数の個数で意味が変わる | オーバーロード |

戻り値の型:void・never・undefined
TypeScript では return と throw の使い方によって、関数の戻り値型が変わります。この3つの型は混同しやすいので整理します。
void — 値を返さない関数
void は「意味のある値を返さない」ことを表します。return を書かない関数、または return; だけの関数に対して TypeScript が推論する型です。
function logMessage(message: string): void {
console.log(message);
// return を書かない(または return; だけ)
}
// void な関数の戻り値を使おうとすると型エラー
const result = logMessage('hello');
result.toUpperCase(); // Error: 'result' は 'void' 型void は undefined と似ていますが、「戻り値を使ってはいけない」という意図を型で表せる点が異なります。コールバック型によく登場します。
// イベントハンドラは「戻り値を使わない」ことを void で表す
type ClickHandler = (event: MouseEvent) => void;
// 戻り値が void な関数型には、実際に値を返す関数も代入できる
const handler: ClickHandler = (e) => e.target; // エラーにならないnever — 絶対に戻らない関数
never は「関数が正常に終了しない」ことを表します。throw する関数や無限ループする関数の戻り値型です。
// throw する関数の戻り値型は never
function fail(message: string): never {
throw new Error(message);
}
// 無限ループも never
function infiniteLoop(): never {
while (true) {}
}never の実用的なメリットは、型の絞り込みに使えることです。
function assertDefined<T>(val: T | null | undefined, label: string): T {
if (val == null) {
fail(`${label} は必須です`); // never を返すので、以降 val は T 型に確定
}
return val; // ここでは val: T
}fail() が never を返すため、TypeScript はその後のコードには val が null / undefined でないと判断できます。
undefined — undefined という値を返す関数
undefined は「undefined という値そのものを返す」ことを表します。
function findUser(id: number): User | undefined {
return users.find((u) => u.id === id); // 見つからないと undefined
}void と undefined の違いをまとめます。
| 型 | 意味 | 戻り値を変数に代入できる? |
|---|---|---|
void | 意味のある値を返さない | できる(ただし void 型になる) |
undefined | undefined という値を返す | できる(undefined として扱える) |
never | 絶対に戻ってこない | 到達不能なので意味がない |

ちゃんと使うためのポイント
-
引数と戻り値の型は明示的に書く。型推論に任せるのは
returnが単純な場合だけ ?は「渡されなかったこと」を区別したいとき、デフォルト値は省略時の値が決まっているとき- コールバックの実装は型定義より少ない引数で書いてよい。
Function型は使わず、引数と戻り値を具体的に書く voidは「戻り値を使わない」、neverは「絶対に戻らない」、undefinedは「undefinedを返す」- オーバーロードよりユニオン型やジェネリクスで表現できないか先に検討する
この章で登場した Comparator<T> や assertDefined<T> の <T> の正体が、次章のテーマであるジェネリクスです。「どんな型でも受けられて、かつ型の対応関係は保つ」仕組みを学びます。
参考リンク
- TypeScript Handbook — More on Functions(英語) — 関数型・オーバーロードの公式リファレンス
- MDN — 関数 — JavaScript側の関数の基礎
