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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?