- TypeScript
TypeScript void型とundefinedの違い|戻り値なし関数の型
TypeScriptのvoid型は「戻り値を使わない」ことを表す型で、undefined型は「undefined という値そのもの」を表す型です。 戻り値のない関数を実行すると、どちらの場合も実行時には undefined が返りますが、型としての役割が異なります。void は「戻り値を見ないでください」という約束、undefined は「必ず undefined が入っています」という約束です。
この違いがいちばんはっきり出るのがコールバックです。戻り値 void のコールバックには値を返す関数を渡せるのに、function 宣言に : void と書いて値を返すと TS2322 になります。 この記事では、その差を tsc のエラー全文と node の実行結果で再現し、戻り値 undefined の関数で return を省けるようになった TypeScript 5.1 以降の挙動も、複数の版で比べます。
確認環境:Ubuntu 24.04 / Node.js 22.22.2 / TypeScript 7.0.2(npx tsc --version の結果)。版の比較には 5.0.4・5.1.6・5.9.3・6.0.3 も使いました。strict: true で確認しています。
void・undefined・never・unknown の早見表
| 型 | 戻り値の型に書いたとき | 変数の型に書いたとき | 代入できる値 |
|---|---|---|---|
void |
return なし、return;、return undefined; が書ける。値を返すと TS2322 |
使い道はほぼない | undefined だけ(null や 1 は TS2322) |
undefined |
return なしでもよい(5.1 以降)。undefined 以外を返すと TS2322 |
「まだ値がない」を表す。ユニオンで使うことが多い | undefined だけ |
never |
最後まで到達するコードがあると TS2534 | 何も代入できない | なし(undefined も TS2322) |
unknown |
何を返してもよい。return がないと TS2355 |
何でも入る。使う前に絞り込みが必要 | すべての値 |
void と undefined の向きの関係は次のとおりです。
| 代入 | 結果 |
|---|---|
undefined 型の値 → void 型の変数 |
できる |
void 型の値 → undefined 型の変数 |
TS2322 Type 'void' is not assignable to type 'undefined'. |
戻り値 void のコールバックに、値を返す関数を渡す |
できる |
戻り値 undefined のコールバックに、値を返す関数を渡す |
TS2322 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「TypeScript void型とundefinedの違い|戻り値なし関数の型」
戻り値のない関数は void と推論される
戻り値の型を書かなかった関数が、どう推論されるかを tsc --declaration で出力しました。
export function plain() {}
export function plainReturn() { return; }
export function plainUndef() { return undefined; }
export async function sendLog2() {}
export const r = plain();
// 出力された .d.ts
export declare function plain(): void;
export declare function plainReturn(): void;
export declare function plainUndef(): undefined;
export declare function sendLog2(): Promise<void>;
export declare const r: void;
| 関数の中身 | 推論される戻り値の型 |
|---|---|
return がない |
void |
return; だけ |
void |
return undefined; |
undefined |
async で return がない |
Promise<void> |
return を書かない関数は void、return undefined; と値を明示した関数は undefined になります。
: void と書いた関数で書けること・書けないこと
function a(): void {}
function b(): void { return; }
function c(): void { return undefined; }
function d(): void { return null; }
function e(): void { return 1; }
ret.ts(4,22): error TS2322: Type 'null' is not assignable to type 'void'.
ret.ts(5,22): error TS2322: Type 'number' is not assignable to type 'void'.
: void と書いた関数から返せるのは undefined だけです。null も数値もエラーになります。
コールバックの void:値を返す関数を渡せるのに、宣言ではエラーになる
再現コード
Array.prototype.push は、追加後の配列の長さ(number)を返します。それを forEach のコールバックにそのまま書いても、エラーにはなりません。
// callback.ts
const source = [10, 20, 30];
const copied: number[] = [];
// (A) push は新しい長さ(number)を返すが、forEach のコールバックに渡せる
source.forEach((v) => copied.push(v));
console.log("copied:", copied);
// (B) 戻り値 void の関数型に、値を返す関数を代入できる
type Logger = (message: string) => void;
const log: Logger = (message) => message.length;
const result = log("hello");
console.log("実行時の戻り値:", result);
// (C) function 宣言に : void と書いて値を返す
function save(v: number): void {
return copied.push(v);
}
// (D) アロー関数でも、関数自身に : void と書くと同じ
const save2 = (v: number): void => copied.push(v);
$ npx tsc -p .
callback.ts(16,3): error TS2322: Type 'number' is not assignable to type 'void'.
callback.ts(20,36): error TS2322: Type 'number' is not assignable to type 'void'.
--locale ja を付けた場合です。
callback.ts(16,3): error TS2322: 型 'number' を型 'void' に割り当てることはできません。
callback.ts(20,36): error TS2322: 型 'number' を型 'void' に割り当てることはできません。
エラーになったのは (C) と (D) だけで、(A) と (B) は通っています。 (C)(D) を消して実行した結果です。
$ node callback.ts
copied: [ 10, 20, 30 ]
実行時の戻り値: 5
(B) の result は型の上では void ですが、実行時には 5 が入っています。void は「undefined が入っている」という意味ではないことが、ここからわかります。
なぜ差が出るのか
| 場面 | void の意味 | 値を返すと |
|---|---|---|
関数型の戻り値(() => void) |
呼び出す側が戻り値を使わない | 通る(返した値は無視される) |
関数自身の戻り値注釈(function f(): void、(): void => ...) |
この関数は値を返さない | TS2322 |
forEach の型定義は、コールバックを (value, index, array) => void として受け取ります。これは「forEach は戻り値を見ません」という宣言です。戻り値を見ない相手に、たまたま値を返す関数を渡しても困ることはないので、TypeScript は通します。この仕組みがあるおかげで、forEach((v) => copied.push(v)) のような短い書き方ができます。
一方、関数自身に : void と書くのは「この関数は値を返さない」という自分への約束なので、値を返す return はエラーになります。
オブジェクトのメソッドでも同じ動きです。型が先に決まっていて、そこへ関数を当てはめる書き方なら通ります。
const o: { run(): void } = { run() { return 1; } }; // 通る
const p: { run: () => void } = { run: function () { return 1; } }; // 通る
戻り値 undefined のコールバックは値を返せない
コールバックの戻り値を void・undefined・unknown に変えて比べました。
function each1(items: number[], fn: (n: number) => void) { for (const i of items) fn(i); }
function each2(items: number[], fn: (n: number) => undefined) { for (const i of items) fn(i); }
function each3(items: number[], fn: (n: number) => unknown) { for (const i of items) fn(i); }
const out: number[] = [];
each1([1], (n) => out.push(n));
each2([1], (n) => out.push(n));
each3([1], (n) => out.push(n));
each1([1], (n) => {});
each2([1], (n) => {});
each3([1], (n) => {});
each2([1], (n) => void out.push(n));
cb.ts(6,19): error TS2322: Type 'number' is not assignable to type 'undefined'.
| コールバックの戻り値の型 | (n) => out.push(n) |
(n) => {} |
|---|---|---|
void |
通る | 通る |
undefined |
TS2322 | 通る |
unknown |
通る | 通る |
コールバックを受け取る側を書くときは、戻り値を使わないなら void にしておくと、渡す側が自由に書けます。
void の戻り値を使おうとしたときのエラー
戻り値 void の関数の結果を使うと、いくつかのエラーが出ます。
// use.ts
type Logger = (message: string) => void;
const log: Logger = (message) => message.length;
const result = log("hello");
if (result) {
console.log("値あり");
}
const n: number = result;
const len = result.toFixed();
const u: undefined = result;
const ok = result === undefined;
use.ts(5,5): error TS1345: An expression of type 'void' cannot be tested for truthiness.
use.ts(8,7): error TS2322: Type 'void' is not assignable to type 'number'.
use.ts(9,20): error TS2339: Property 'toFixed' does not exist on type 'void'.
use.ts(10,7): error TS2322: Type 'void' is not assignable to type 'undefined'.
TS1345 の日本語の文面です。
use.ts(5,5): error TS1345: 'void' 型の式は、真実性をテストできません。
| エラー | 意味 | 対応 |
|---|---|---|
| TS1345 | void の値を if などで真偽判定した |
関数の戻り値の型を boolean などに変える。判定したい値を返す設計にする |
| TS2322(void → number) | void の値を別の型の変数に入れた | 同上 |
| TS2339 | void の値のプロパティを読んだ | 同上 |
| TS2322(void → undefined) | void は undefined と同じではない | 戻り値を undefined 型にするか、使わない |
最後の行の result === undefined はエラーになりませんでした。比較そのものは書けますが、先ほど見たとおり実行時に undefined とは限らないため、void の戻り値には頼らないのが基本です。TS1345 が出たら、「この関数は結果を返す設計になっているか」を見直す合図と考えるとよいでしょう。
戻り値 undefined の関数で return を省ける(TS 5.1 以降)
版ごとの比較
次のファイルを、5つの版で型チェックしました。
// undef.ts
function noReturn(): undefined {}
function partial(flag: boolean): undefined {
if (flag) {
return;
}
}
declare function takes(fn: () => undefined): void;
takes(() => {});
takes(function () {});
function num(): number {}
TypeScript 5.0.4 の出力(--noImplicitReturns つき)です。
undef.ts(1,22): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
undef.ts(3,34): error TS7030: Not all code paths return a value.
undef.ts(10,7): error TS2345: Argument of type '() => void' is not assignable to parameter of type '() => undefined'.
Type 'void' is not assignable to type 'undefined'.
undef.ts(11,7): error TS2345: Argument of type '() => void' is not assignable to parameter of type '() => undefined'.
Type 'void' is not assignable to type 'undefined'.
undef.ts(13,17): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
TypeScript 5.1.6・5.9.3・6.0.3・7.0.2 の出力(4つとも同じ。--noImplicitReturns の有無でも同じ)です。
undef.ts(13,17): error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
| 書き方 | 5.0.4 | 5.1.6 | 5.9.3 | 6.0.3 | 7.0.2 |
|---|---|---|---|---|---|
function f(): undefined {}(return なし) |
TS2355 | 通る | 通る | 通る | 通る |
一部の経路だけ return;(noImplicitReturns) |
TS7030 | 通る | 通る | 通る | 通る |
() => undefined を求める引数に () => {} を渡す |
TS2345 | 通る | 通る | 通る | 通る |
function f(): number {}(return なし) |
TS2355 | TS2355 | TS2355 | TS2355 | TS2355 |
5.1 以降は、戻り値 undefined の関数でも return を省けます。 エラー文も neither 'void' nor 'any' から neither 'undefined', 'void', nor 'any' に変わっています。TypeScript 5.1 のリリースノート(Easier Implicit Returns for undefined-Returning Functions)にも、同じ3点(return の省略、undefined を返す関数としての推論、noImplicitReturns での扱い)が書かれています。
それでも void と undefined は別の型
return を省けるようになっても、型としての違いは残っています。
| 比べる点 | : void |
: undefined |
|---|---|---|
return なし |
書ける | 書ける(5.1 以降) |
| 呼び出した結果の型 | void(真偽判定で TS1345) |
undefined(判定や比較に使える) |
| コールバックの型にしたとき | 値を返す関数も渡せる | 値を返す関数は TS2322 |
| 伝わる意図 | 戻り値は使わないでほしい | 必ず undefined を返す |
迷ったら、戻り値のない関数には void を書きます。 undefined を選ぶのは、「見つからなければ undefined」のように、undefined という値に意味を持たせる場合(たいていは string | undefined のようなユニオン)です。
引数に書いた void
引数の型に void を書くと、その引数は省略できます。undefined 型の引数とは扱いが違いました。
function f(x: void): void {}
f();
f(undefined);
function g(x: undefined): void {}
g();
ret.ts(10,1): error TS2554: Expected 1 arguments, but got 0.
エラーになったのは g() だけです。この性質は、次の Promise の resolve で役に立ちます。
Promise の void と async 関数
結果の値を持たない非同期処理
結果として渡す値がない非同期関数は、戻り値を Promise<void> と書きます。
// promise.ts
async function wait(ms: number): Promise<void> {
await new Promise<void>((resolve) => setTimeout(resolve, ms));
}
async function nothing(): Promise<void> {}
const r1 = await wait(10);
const r2 = await nothing();
console.log("await wait(10) →", r1);
console.log("await nothing() →", r2);
console.log(nothing());
$ node promise.ts
await wait(10) → undefined
await nothing() → undefined
Promise { undefined }
resolve() を引数なしで呼ぶには void を書く
new Promise の型引数を書かない、または void 以外にすると、resolve() を引数なしで呼べません。
const p1 = new Promise<void>((resolve) => resolve());
const p2 = new Promise((resolve) => resolve());
const p3 = new Promise<number>((resolve) => resolve());
promise.ts(8,37): error TS2794: Expected 1 arguments, but got 0. Did you forget to include 'void' in your type argument to 'Promise'?
promise.ts(9,45): error TS2794: Expected 1 arguments, but got 0. Did you forget to include 'void' in your type argument to 'Promise'?
promise.ts(8,37): error TS2794: 1 引数が必要ですが、0 が指定されました。'Promise' の型引数に 'void' を含めましたか?
エラー文が解決策を教えてくれています。new Promise<void>(...) と書けば、resolve() を引数なしで呼べます。
async 関数でも値を返すとエラーになる
async function saveAll(): Promise<void> {
return 1;
}
async function main() {
const r = await wait(1);
if (r) {
console.log(r);
}
}
promise.ts(13,3): error TS2322: Type 'number' is not assignable to type 'void'.
promise.ts(24,7): error TS1345: An expression of type 'void' cannot be tested for truthiness.
Promise<void> の中身も void なので、同期の関数と同じ規則が当てはまります。await した結果を if で判定すると TS1345 です。
なお、[1, 2].forEach(async (n) => { await wait(n); }) のように、戻り値 void のコールバックへ async 関数を渡す書き方は、エラーになりませんでした。Promise を返す関数も「値を返す関数」の一種として通るためです。この場合 forEach は各処理の完了を待たない点に注意します。
void 演算子:式の結果を undefined にする
型の void とは別に、JavaScript には void 演算子があります。式を評価したうえで、結果を必ず undefined にする演算子です。
// voidop.ts
console.log(void 0);
console.log(void "abc");
console.log(typeof void 0);
console.log(void 0 === undefined);
const list: number[] = [];
// アロー関数の戻り値を捨てる
const add = (n: number) => void list.push(n);
const added = add(1);
console.log("add(1) →", added, "/ list:", list);
// map に渡すと undefined の配列になる
console.log([1, 2].map((n) => void list.push(n)));
// Promise を待たずに呼びっぱなしにする
async function sendLog(message: string): Promise<void> {
await new Promise<void>((resolve) => setTimeout(resolve, 10));
console.log("送信完了:", message);
}
function onClick(): void {
void sendLog("clicked");
console.log("onClick は先に終わる");
}
onClick();
$ node voidop.ts
undefined
undefined
undefined
true
add(1) → undefined / list: [ 1 ]
[ undefined, undefined ]
onClick は先に終わる
送信完了: clicked
| 書き方 | 働き | 使う場面 |
|---|---|---|
void 0 |
undefined を得る |
短く確実に undefined を書きたいとき(ビルド後のコードでよく見かける) |
(n) => void list.push(n) |
式を実行し、戻り値を捨てる | 戻り値 undefined を求めるコールバックに、値を返す式を渡したいとき |
void sendLog("clicked") |
Promise を待たないことを明示する | 完了を待たずに先へ進む呼び出し |
void 演算子を付けたアロー関数は、戻り値の型が undefined と推論されました。
// 出力された .d.ts
export declare const add: (n: number) => undefined;
export declare const v: undefined; // export const v = void 0;
型の void と演算子の void は別のもので、演算子の結果は型の上でも実行時でも undefined です。先ほどの each2([1], (n) => void out.push(n)) がエラーにならなかったのは、このためです。
戻り値の型をどれにするかの判断表
| 関数の性質 | 戻り値の型 |
|---|---|
| 処理だけ行い、結果を返さない | void |
| 非同期で処理だけ行い、結果を返さない | Promise<void> |
| 値があるときとないときがある | string | undefined などのユニオン |
| 必ず undefined を返すことに意味がある | undefined |
| 必ず例外を投げる、終わらない | never |
| 受け取るコールバックの戻り値を使わない | () => void |
| 受け取るコールバックの戻り値を使う | () => boolean など具体的な型 |
よくある質問
Q. void と undefined は、結局どちらを書けばよいですか?
A. 戻り値のない関数には void を書きます。呼び出す側に「戻り値は使わない」という意図が伝わり、戻り値を if で判定するような書き方を TS1345 で止められます。undefined は、undefined という値を結果として返すことに意味がある場合に選びます。
Q. 戻り値 void のコールバックに値を返す関数を渡せるのは、型の抜け穴ではありませんか?
A. 意図された仕様です。受け取る側が戻り値を使わないと宣言しているので、渡す関数が何を返しても影響がありません。おかげで forEach((v) => copied.push(v)) のように書けます。関数自身に : void と書いた場合は TS2322 になるので、値を返さない約束は守られます。
Q. TS1345 が出たときは、どう直せばよいですか?
A. 判定に使っている関数の戻り値が void になっています。その関数が成否などを返す設計なら戻り値の型を boolean などに変え、返さない設計なら判定をやめます。Promise<void> を await した結果でも同じです。
Q. function f(): undefined {} がエラーになります。
A. TypeScript 5.0.4 では TS2355 になり、5.1.6 以降では通ることを確かめました。使っている TypeScript の版を npx tsc --version で確認してください。5.0 以前では return undefined; を書くか、戻り値を void にします。
まとめ
- void は「戻り値を使わない」、undefined は「undefined という値」。実行時はどちらも undefined が返ることが多いが、型の役割が違う
- 戻り値 void の関数型には、値を返す関数を渡せる。
forEach((v) => arr.push(v))が書けるのはこのため - 関数自身に
: voidと書いて値を返すと TS2322 - void の戻り値を
ifで判定すると TS1345。実行時に undefined とは限らないので、void の戻り値には頼らない - 戻り値
undefinedの関数は、5.1 以降returnを省ける(5.0.4 は TS2355、5.1.6〜7.0.2 は通る) - 結果のない非同期処理は
Promise<void>。new Promise<void>と書くとresolve()を引数なしで呼べる - void 演算子は式の結果を undefined にする。戻り値を捨てる、Promise を待たないことを示す、という使い方がある
参考
- TypeScript 5.1 リリースノート「Easier Implicit Returns for undefined-Returning Functions」: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「TypeScript void型とundefinedの違い|戻り値なし関数の型」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。