こんにちは!TypeScriptの型システムの世界へようこそ。
フルスタックアーキテクトの先輩として、今日から君をさらに一歩上のステージへ導く「とっておきの知見」を授けましょう。
今回は、関数における「`void`」と「`undefined`」の戻り値の決定的な違いについて深掘りします。「えっ、何も返さない関数なら、どっちでも一緒じゃないの?」なんて思っていませんか?
ここをクリアすれば、TypeScriptの型安全性の本質が見えてきます。一緒にしっかりとマスターしていきましょう!
—
1. そもそも `void` と `undefined` って何が違うの?
まずは、それぞれの基本的な意味をおさらいしておきますね。
- `undefined`: 「値が存在する」という状態。ただし、その値が「未定義(何も代入されていない)」であることを表すJavaScriptのプリミティブ値そのものです。
- `void`: 「戻り値は意図的に無視される(意味を持たない)」というTypeScript特有の型。実行時には `undefined` が返ることが多いですが、型システム的には「呼び出し元は、この関数の戻り値を使ってはいけない」という強い制約を意味します。
イメージ図で表すと、こんな感じです。
【 戻り値の型ごとの世界観 】
undefined型 ──> 「ここに『undefinedという値』が存在します。使ってもいいですよ」
void型 ──> 「ここは『何も見ちゃダメなブラックボックス』です。戻り値は捨てられます」
—
2. なぜ、戻り値が `void` の関数で `undefined` を返してはいけないのか?
さて、ここからが本題です。
例えば、「ログを出力するだけ」の関数を作るとします。何も値を返さないので、戻り値を `void` に設定しました。このとき、うっかり `return undefined;` と書いてしまうとどうなるでしょうか?
実際のコードを見てみましょう。
// 戻り値を「void」と宣言した関数
function logMessage(message: string): void {
console.log(`[LOG]: ${message}`);
// ❌ やってしまいがちなミス:明示的に undefined を返している
return undefined;
// 【コンパイルエラー!】
// 型 ‘undefined’ を型 ‘void’ に割り当てることはできません。
}
「えっ、`void` の実体って `undefined` なんでしょ? なぜエラーになるの?」と思いますよね。ここにはTypeScriptの「呼び出し元を守るための優しさ(型安全性の担保)」が隠されています。
TypeScriptがエラーにする理由:契約の違反を防ぐため
TypeScriptにおいて、関数の戻り値の型は「呼び出し元との契約(Contract)」です。
- `void` の契約: 「この関数を呼んでも、便利な戻り値なんて何も返ってこないから、受け取って処理に使おうなんて思わないでね」
- `undefined` の契約: 「この関数は、明確に `undefined` という値を返すので、もし必要ならチェックしてね」
もし、`void` と宣言された関数の中で `undefined` を返すことを許可してしまうと、呼び出し元で次のような危険なコードが書けてしまいます。
// 仮に void が undefined を返すことを許してしまった場合の世界線…
function doSomething(): void {
return undefined; // 許されたとする
}
// 呼び出し元
const result = doSomething();
// 「戻り値がある」と誤認して、さらにプロパティにアクセスしようとするバグを呼びかねない
// result.toString(); // 実行時に “Cannot read properties of undefined” でクラッシュ!
TypeScriptのコンパイラは、私たちがこうした「意図しないバグ」の沼にハマるのを未然に防ぐために、`void` 型の関数内で `undefined` を(明示的にせよ)返すことを厳しくブロックしてくれているのです。
—
3. コールバック関数を扱うときに見えやすくなる「罠」
この `void` と `undefined` の違いが最も牙をむく(そして初心者が一番ハマる)のが、コールバック関数を型定義する時です。
例えば、「何かを受け取って処理し、何も返さない関数」を引数に取る関数を作るとしましょう。
// 引数として「何も返さない関数(void)」を受け取る高階関数
function processUser(callback: (name: string) => void) {
// ユーザー処理…
callback(“Taro”);
}
// パターンA: 戻り値がない通常の関数を渡す(OK)
processUser((name) => {
console.log(name);
});
// パターンB: 意図せず undefined を返す関数を渡そうとする(エラーになる!)
processUser((name) => {
console.log(name);
return undefined; // ❌ ここで型エラー!
});
「え、でもArrayの `forEach` とかでは `undefined` 返しても怒られない気がするけど……?」と思った鋭いあなた、素晴らしい着眼点です!
実は、TypeScriptの型システムには 「`void` の亜種(サブタイプ)の許容」 という柔軟なルールがあります。
`void` 型が持つ特別な寛容さ
TypeScriptでは、ある関数が `void` を返すことを要求している(期待している)場合、実際に何を返そうが(あるいは何も返そうが)、それを無視して受け入れるという仕様があります。
// 戻り値が void を期待されている場所には…
const f: () => void = () => {
return “Hello”; // なんと、文字列を返してもエラーにならない!
};
えっ、さっきと言っていることが違うじゃん!と思われるかもしれません。整理しましょう。
1. 関数を「定義」するとき: `function foo(): void { return undefined; }` のように、関数自身の型注釈で `void` と書いた場合、明示的に `undefined` や他の値を `return` することは原則として禁止されます(バグを防ぐため)。
2. 関数を「代入・引数として渡す」とき: 呼び出し側が「別に何を返しても無視するよ(`void` で受けるよ)」と言っている場所には、`undefined` や何かしらの値を返す関数を渡すことができます。
この挙動は、JavaScriptの一般的なライブラリ(例えば `Array.prototype.forEach` などは、コールバックの戻り値を意図的に捨てます)と既存のコードベースをうまく共存させるための、TypeScriptの高度な設計思想に基づいています。
—
4. 安全な設計のためのベストプラクティス
現場でコードを書くとき、私たちはどう設計すべきでしょうか? 迷ったときは以下の原則を思い出してください。
① 本当に何も返さないなら、`return` 文自体を書かない
`void` を返す関数では、そもそも `return` キーワードを書く必要はありません。処理の最後まで到達すればそれで十分です。
// 模範的なコード
function saveToDatabase(data: object): void {
// データベース保存処理
console.log(“Saved!”, data);
// return は書かない!
}
② もし「何もない」という値を明示的に返したいなら、型に `undefined` を使う
もし呼び出し元に対して「この関数は正常終了した証として `undefined` を返すよ」と明確に契約したい場合は、`void` ではなく `undefined` を使いましょう。
// 戻り値として undefined を許容・期待する設計
function findUserById(id: number): string | undefined {
if (id === 1) {
return “Taro”;
}
// 見つからなかった場合は明確に undefined を返す
return undefined;
}
—
まとめ
いかがでしたでしょうか? 今回のポイントをギュッと凝縮して振り返ります。
- `void` は「戻り値を使わせないための防壁(コンパイラへの誓約)」。
- `undefined` は「実際に `undefined` という値が存在するという事実」。
- 自分で定義する `void` 関数の中で `return undefined;` と書くのは、型安全性の観点からナンセンス(かつコンパイルエラーになる)。
- コールバックなどで `void` が期待されている場所では、柔軟性のために値の返却が許容されるケースもあるが、基本は「何も返さない」のが最もクリーン。
ここをしっかりと理解できれば、TypeScriptの型がなぜそのエラーを出しているのか、コンパイラの意図が手に取るようにわかるようになります。
基礎から本質までを理解したあなたなら、もう怖くありません。明日からのコーディングをさらに楽しんでいきましょう!バッチリマスターできましたね!