TypeScriptを学習する上で、最初に出会う型の一つがvoid型です。
JavaScriptには存在しない概念であるため、戸惑う方も多いかもしれません。
void型を正しく理解することは、バグの少ない堅牢なコードを書くための第一歩となります。
本記事では、void型の基本的な意味から、undefinedとの決定的な違い、そして実務で役立つ具体的な使い方までを詳しく解説します。
TypeScriptにおけるvoid型の基本的な意味
TypeScriptにおけるvoid型は、「値が存在しないこと」を意味する特別な型です。
主に、関数が値を返さない場合、つまり戻り値を持たない関数を定義する際に使用されます。
一般的なプログラミング言語であるC言語やJavaなどでも、同様の目的でvoidが使われています。
「戻り値がない」ことを明示する重要性
関数にvoid型を指定することで、その関数が「副作用(コンソール出力やデータの保存など)」を目的としており、計算結果を返すものではないことを開発者に伝えることができます。
これにより、コードの意図が明確になり、誤って戻り値を利用しようとするミスを未然に防げます。
void型が使用される主なケース
最も一般的なケースは、以下のようにコンソールにログを出力するだけの関数です。
// コンソールにメッセージを表示するだけの関数
function logMessage(message: string): void {
console.log(message);
// return文がない、または値のないreturn文のみ
}
logMessage("Hello TypeScript!");
Hello TypeScript!
上記のコードでは、logMessage関数は何も返さないため、戻り値の型としてvoidが指定されています。
voidとundefinedの決定的な違い
TypeScriptにはvoidと似た概念としてundefinedが存在します。
初心者の多くが「戻り値がないならundefinedでも良いのではないか」という疑問を抱きますが、この2つには明確な使い分けがあります。
型としての性質の違い
undefinedはJavaScriptのプリミティブな値そのものを指す型です。
一方で、voidは「戻り値を利用してはいけない」というセマンティクス(意味論)を含んでいます。
たとえ関数が実行結果として内部的にundefinedを返していたとしても、型定義がvoidであれば、その値を受け取って何か操作を行うことは禁止されます。
代入可能性と厳密なチェック
TypeScriptのtsconfig.jsonでstrictNullChecksが有効な場合、void型とundefined型は厳密に区別されます。
// void型を返す関数
function voidFunction(): void {
return;
}
// undefined型を返す関数
function undefinedFunction(): undefined {
return undefined; // 明示的にundefinedを返す必要がある
}
let v: void = voidFunction();
let u: undefined = undefinedFunction();
// エラー:void型の値はundefined型に代入できない
// u = v;
void型の変数は、事実上undefined(またはstrictNullChecksがオフの場合はnull)しか代入できませんが、用途としては関数の戻り値に限定するのが一般的です。
return文の有無による挙動の差
voidを戻り値に持つ関数では、return文を省略することができます。
しかし、戻り値をundefinedと定義した場合、明示的にreturn undefined;と記述するか、値のないreturn;を書く必要があります。
この「何も書かなくて良い」という利便性が、voidが関数戻り値の標準として使われる理由の一つです。
関数の戻り値としてvoidを活用するメリット
実務的な開発において、voidを適切に使用することには多くのメリットがあります。
意図しない戻り値の利用を防ぐ
もし戻り値の型を記述しなかった場合、TypeScriptは推論を行いますが、意図せず値を返してしまうコードを書いてしまった際に警告が出ません。
voidを明示することで、「この関数は何も返さない設計である」という契約を型システム上で定義できます。
コードの可読性とメンテナンス性の向上
型定義を見るだけで、その関数が「計算(Query)」なのか「状態変更(Command)」なのかを即座に判断できます。
コマンド・クエリ分離(CQS)という原則に基づいた美しい設計を維持しやすくなります。
実践的なvoid型の使い方
より高度な開発シーンでのvoidの挙動を確認しましょう。
コールバック関数におけるvoidの挙動
TypeScriptには、コールバック関数の戻り値の型がvoidとして定義されている場合、「実際の関数が何か値を返してもエラーにしない」という特別なルールがあります。
function doSomething(callback: () => void): void {
callback();
}
// 数値を返しているがエラーにならない
doSomething(() => {
return 42;
});
これは、Array.prototype.forEachなどの既存のJavaScript APIとの互換性を保つための仕様です。
forEachは戻り値を無視するため、コールバック関数がたまたま値を返してしまったとしても、安全に無視できるからです。
高階関数でのvoidの扱いと注意点
関数を引数に取る関数(高階関数)を自作する場合、この挙動を理解しておくことは非常に重要です。
voidと指定されているからといって、「戻り値が絶対にundefinedである」ことを保証するものではないという点に注意してください。
あくまで「呼び出し側が戻り値を使わない」ことを約束する型なのです。
void型に関するよくある間違いと落とし穴
voidを使いこなすために、間違いやすいポイントを整理しておきましょう。
変数宣言にvoid型を使用する場合
変数の型としてvoidを指定することは可能ですが、実用性はほとんどありません。
let unusable: void = undefined;
このような定義は、ジェネリクスの引数などで型を消去したい場合などの特殊な状況を除き、避けるべきです。
typeとinterfaceでの挙動の違い
関数の型定義をtypeエイリアスやinterfaceで行う際も、戻り値のvoidは同様の「寛容な」挙動を示します。
| 定義方法 | 戻り値の型 | 値を返した場合の挙動 |
|---|---|---|
| 関数宣言 | void | コンパイルエラー(厳密) |
| 関数式(型注釈あり) | void | コンパイルエラー(厳密) |
| コールバック/型定義としての指定 | void | 許容される(無視される) |
この違いを理解していないと、なぜエラーが出る場合と出ない場合があるのか混乱する原因になります。
voidとneverの違い
voidと混同されやすいもう一つの型にneverがあります。
voidは「戻り値がない(が、関数は正常に終了する)」ことを意味します。
対してneverは「関数の終わりに到達しない」ことを意味します。
例えば、常に例外をスローする関数や、無限ループを持つ関数にはneverを指定します。
function throwError(message: string): never {
throw new Error(message);
}
適切に使い分けることで、プログラムの制御フローをより正確に表現できます。
モダンなTypeScript開発におけるvoidの設計思想
2026年現在の開発においても、voidの重要性は変わりません。
特に非同期処理(async/await)を扱う場合、Promise<void>という形での利用が非常に増えています。
非同期の副作用を伴う関数では、完了を待機する必要があるものの、結果として返すデータがない場合にこの型が活躍します。
async function saveData(data: any): Promise<void> {
await api.post("/save", data);
// 保存完了。値を返す必要はない
}
このように、モダンなフロントエンド開発からバックエンド(Node.js/Deno/Bun)開発に至るまで、voidは不可欠な存在です。
まとめ
TypeScriptのvoid型は、単に「何もない」ことを示すだけでなく、関数の戻り値を利用させないという意図を明確にするための強力なツールです。
undefinedとの違いを理解し、特にコールバック関数における特殊な挙動を把握することで、型定義の柔軟性と安全性を両立させることができます。
関数の設計時に「この関数は値を返す必要があるか?」を常に問いかけ、不要であれば積極的にvoidを活用しましょう。
正確な型定義は、自分自身だけでなく、チームメンバーにとっても読みやすく、メンテナンスしやすいコードへと繋がります。
本記事を通じて、void型の本質的な意味と実践的な使い方が整理できれば幸いです。
