はじめに

TypeScriptを使って開発を行う上で、関数に型を付けることは非常に重要です。関数の引数や戻り値に型を定義することで、呼び出し側でのミスを未然に防ぎ、エディタによる強力な補完を受けられます。

本記事では、TypeScriptにおける関数の型定義について、基本的な型注釈から、アロー関数の型定義、オプショナル引数、デフォルト引数、残余引数(rest parameters)、そして関数オーバーロードまで、段階的に解説します。

この記事を読み終えると、以下のことができるようになります。

  • 関数の引数と戻り値に適切な型注釈を付けられる
  • アロー関数に対して型を定義する方法を理解できる
  • オプショナル引数とデフォルト引数を使い分けられる
  • 残余引数(rest parameters)に型を付けられる
  • 関数オーバーロードで複数のシグネチャを定義できる
  • 関数型を変数や型エイリアスとして定義できる

実行環境・前提条件

前提知識

  • JavaScriptの関数構文(function宣言、アロー関数)の理解
  • TypeScriptの基本的な型(基本型入門を参照)
  • 配列とタプルの型定義(配列・タプル入門を参照)

動作確認環境

ツール バージョン
Node.js 20.x以上
TypeScript 5.7以上
VS Code 最新版

本記事のサンプルコードは、TypeScript Playgroundで動作確認できます。ローカル環境で実行する場合は、開発環境構築ガイドを参照してください。

期待される結果

本記事のコードを実行すると、以下の動作を確認できます。

  • 関数の引数と戻り値の型が正しくチェックされる
  • 型に合わない引数を渡すとコンパイルエラーが発生する
  • オーバーロードによって引数の組み合わせに応じた型推論が行われる

TypeScriptにおける関数型の全体像

TypeScriptでは、関数に対してさまざまな方法で型を定義できます。まず、関数型の全体像を把握しましょう。

graph TB
    A[TypeScriptの関数型] --> B[引数の型]
    A --> C[戻り値の型]
    A --> D[関数シグネチャ]
    B --> E[必須引数]
    B --> F[オプショナル引数]
    B --> G[デフォルト引数]
    B --> H[残余引数]
    D --> I[関数宣言]
    D --> J[アロー関数]
    D --> K[関数オーバーロード]
    D --> L[コールシグネチャ]

関数の基本的な型注釈

関数宣言への型注釈

TypeScriptでは、関数宣言に対して引数と戻り値の型を注釈として付けられます。基本的な構文は以下の通りです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 関数宣言の型注釈
function add(a: number, b: number): number {
  return a + b;
}

// 使用例
const result = add(10, 20);
console.log(result); // 30

// コンパイルエラー: 引数の型が一致しない
// const error = add("10", "20"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'

関数の引数には、パラメータ名の後にコロン(:)と型を記述します。戻り値の型は、引数リストの閉じ括弧の後にコロン(:)と型を記述します。

戻り値の型推論

TypeScriptは戻り値の型を推論できるため、明示的な型注釈を省略することも可能です。ただし、関数のインターフェースを明確にするため、戻り値の型を明示することが推奨されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 戻り値の型推論(型注釈を省略)
function multiply(a: number, b: number) {
  return a * b;
}
// multiply関数の戻り値はnumber型と推論される

// 戻り値の型を明示(推奨)
function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error("Division by zero");
  }
  return a / b;
}

戻り値の型を明示する主なメリットは以下の通りです。

  • 関数のシグネチャが一目で分かる
  • 実装のミスを早期に発見できる
  • ドキュメントとしての役割を果たす

void型 - 戻り値がない関数

関数が何も返さない場合、戻り値の型はvoidを使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// void型の関数
function logMessage(message: string): void {
  console.log(message);
  // returnは省略可能、またはreturn;のみ
}

// 使用例
logMessage("Hello, TypeScript!");

// void型の関数でundefinedを返すことは可能
function logAndReturn(message: string): void {
  console.log(message);
  return; // OK: void型ではreturn文を書くことができる
}

void型とundefined型は異なります。void型の関数は戻り値を使用しないことを意図しており、undefined型の関数は明示的にundefinedを返すことを意図しています。

1
2
3
4
5
6
7
8
9
// void型: 戻り値を使用しないことを意図
function processData(data: string): void {
  console.log(data);
}

// undefined型: 明示的にundefinedを返す
function getUndefined(): undefined {
  return undefined;
}

never型 - 決して戻らない関数

never型は、関数が決して戻らないことを表します。例外をスローする関数や、無限ループを持つ関数に使用されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 例外をスローする関数
function throwError(message: string): never {
  throw new Error(message);
}

// 無限ループを持つ関数
function infiniteLoop(): never {
  while (true) {
    // 永久に終わらない処理
  }
}

// never型を活用した網羅性チェック
type Shape = "circle" | "square" | "triangle";

function getArea(shape: Shape): number {
  switch (shape) {
    case "circle":
      return Math.PI * 10 * 10;
    case "square":
      return 10 * 10;
    case "triangle":
      return (10 * 10) / 2;
    default:
      // すべてのケースを網羅していれば、ここには到達しない
      const exhaustiveCheck: never = shape;
      throw new Error(`Unknown shape: ${exhaustiveCheck}`);
  }
}

アロー関数の型定義

アロー関数への型注釈

アロー関数にも同様に型注釈を付けられます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// アロー関数の型注釈
const subtract = (a: number, b: number): number => {
  return a - b;
};

// 簡略化した記法(式が1つの場合)
const power = (base: number, exponent: number): number => base ** exponent;

// 使用例
console.log(subtract(10, 3)); // 7
console.log(power(2, 8));     // 256

関数型の変数への代入

関数を変数に代入する際、変数に対して関数型を注釈することもできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 変数に関数型を注釈
const greet: (name: string) => string = (name) => {
  return `Hello, ${name}!`;
};

// 関数型を変数に注釈すると、引数の型は推論される
const farewell: (name: string) => string = (name) => {
  return `Goodbye, ${name}!`;
};

console.log(greet("Alice"));    // "Hello, Alice!"
console.log(farewell("Bob"));   // "Goodbye, Bob!"

型エイリアスによる関数型の定義

関数型を再利用したい場合、型エイリアス(type)を使って定義できます。これにより、コードの可読性と保守性が向上します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 型エイリアスで関数型を定義
type BinaryOperation = (a: number, b: number) => number;

// 型エイリアスを使った関数の定義
const add: BinaryOperation = (a, b) => a + b;
const subtract: BinaryOperation = (a, b) => a - b;
const multiply: BinaryOperation = (a, b) => a * b;
const divide: BinaryOperation = (a, b) => a / b;

// 使用例
console.log(add(10, 5));      // 15
console.log(subtract(10, 5)); // 5
console.log(multiply(10, 5)); // 50
console.log(divide(10, 5));   // 2

interfaceによる関数型の定義

interfaceを使って関数型を定義することもできます。これはコールシグネチャと呼ばれます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// interfaceで関数型を定義(コールシグネチャ)
interface Formatter {
  (input: string): string;
}

// interfaceを使った関数の定義
const toUpperCase: Formatter = (input) => input.toUpperCase();
const toLowerCase: Formatter = (input) => input.toLowerCase();

console.log(toUpperCase("hello")); // "HELLO"
console.log(toLowerCase("WORLD")); // "world"

typeinterfaceのどちらを使うかは、プロジェクトの方針に従います。一般的には、関数型にはtypeを使い、オブジェクトの構造定義にはinterfaceを使うことが多いです。

オプショナル引数とデフォルト引数

オプショナル引数

引数を省略可能にするには、パラメータ名の後に?を付けます。オプショナル引数は、必須引数の後に配置する必要があります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// オプショナル引数
function greet(name: string, greeting?: string): string {
  if (greeting) {
    return `${greeting}, ${name}!`;
  }
  return `Hello, ${name}!`;
}

// 使用例
console.log(greet("Alice"));           // "Hello, Alice!"
console.log(greet("Bob", "Good morning")); // "Good morning, Bob!"

オプショナル引数の型は、指定した型とundefinedのユニオン型になります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// オプショナル引数の型
function example(value?: number): void {
  // valueの型は number | undefined
  if (value !== undefined) {
    console.log(value * 2);
  }
}

example();    // undefined
example(10);  // 20

デフォルト引数

引数にデフォルト値を設定すると、引数が省略された場合にそのデフォルト値が使用されます。デフォルト引数は、型推論によって自動的に型が決定されます。

1
2
3
4
5
6
7
8
// デフォルト引数
function greetWithDefault(name: string, greeting: string = "Hello"): string {
  return `${greeting}, ${name}!`;
}

// 使用例
console.log(greetWithDefault("Alice"));           // "Hello, Alice!"
console.log(greetWithDefault("Bob", "Good evening")); // "Good evening, Bob!"

デフォルト引数とオプショナル引数の違いに注意しましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// オプショナル引数: undefinedを渡すとundefinedになる
function withOptional(value?: number): number | undefined {
  return value;
}

// デフォルト引数: undefinedを渡すとデフォルト値が使われる
function withDefault(value: number = 100): number {
  return value;
}

console.log(withOptional(undefined)); // undefined
console.log(withDefault(undefined));  // 100

オプショナル引数とデフォルト引数の使い分け

どちらを使うべきかは、以下の基準で判断します。

使用場面 推奨
引数が省略された場合に特定の値を使いたい デフォルト引数
引数が省略されたことを明示的に判定したい オプショナル引数
undefinedが渡された場合も特定の値にしたい デフォルト引数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// デフォルト引数の実践例: 設定オブジェクト
interface Config {
  timeout: number;
  retries: number;
  debug: boolean;
}

function fetchData(
  url: string,
  config: Config = { timeout: 5000, retries: 3, debug: false }
): void {
  console.log(`Fetching ${url} with timeout ${config.timeout}ms`);
}

fetchData("https://api.example.com");
// "Fetching https://api.example.com with timeout 5000ms"

残余引数(Rest Parameters)

残余引数の基本

残余引数(rest parameters)を使うと、可変長の引数を配列として受け取れます。残余引数は、引数リストの最後に配置し、...(スプレッド構文)を使って定義します。

1
2
3
4
5
6
7
8
9
// 残余引数の基本
function sum(...numbers: number[]): number {
  return numbers.reduce((acc, curr) => acc + curr, 0);
}

// 使用例
console.log(sum(1, 2, 3));           // 6
console.log(sum(10, 20, 30, 40, 50)); // 150
console.log(sum());                   // 0

残余引数と通常の引数の組み合わせ

残余引数は、通常の引数と組み合わせて使用できます。ただし、残余引数は必ず最後に配置する必要があります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 通常の引数と残余引数の組み合わせ
function introduce(greeting: string, ...names: string[]): string {
  const nameList = names.join(", ");
  return `${greeting}, ${nameList}!`;
}

// 使用例
console.log(introduce("Hello", "Alice", "Bob", "Charlie"));
// "Hello, Alice, Bob, Charlie!"

console.log(introduce("Welcome", "Developer"));
// "Welcome, Developer!"

タプル型との組み合わせ

残余引数にタプル型を使用すると、引数の順序と型を厳密に定義できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// タプル型を使った残余引数
function createUser(...args: [string, number, boolean]): void {
  const [name, age, isActive] = args;
  console.log(`Name: ${name}, Age: ${age}, Active: ${isActive}`);
}

// 使用例
createUser("Alice", 30, true);
// "Name: Alice, Age: 30, Active: true"

// コンパイルエラー: 引数の数や型が一致しない
// createUser("Bob", "30", true);
// createUser("Charlie", 25);

可変長タプルと残余引数

TypeScriptでは、タプル型の中で残余要素を使うことができます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 可変長タプル
type StringAndNumbers = [string, ...number[]];

function processData(...args: StringAndNumbers): void {
  const [label, ...values] = args;
  const total = values.reduce((acc, curr) => acc + curr, 0);
  console.log(`${label}: ${total}`);
}

// 使用例
processData("Sum", 1, 2, 3, 4, 5);  // "Sum: 15"
processData("Total", 100);          // "Total: 100"
processData("Empty");               // "Empty: 0"

関数オーバーロード

関数オーバーロードとは

関数オーバーロードは、同じ関数名で異なる引数の型や数に応じた複数のシグネチャを定義する機能です。TypeScriptでは、オーバーロードシグネチャと実装シグネチャを分けて記述します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// オーバーロードシグネチャ
function format(value: string): string;
function format(value: number): string;
function format(value: Date): string;
// 実装シグネチャ
function format(value: string | number | Date): string {
  if (typeof value === "string") {
    return value.toUpperCase();
  } else if (typeof value === "number") {
    return value.toFixed(2);
  } else {
    return value.toISOString();
  }
}

// 使用例
console.log(format("hello"));           // "HELLO"
console.log(format(123.456));           // "123.46"
console.log(format(new Date(2026, 0, 3))); // "2026-01-02T15:00:00.000Z"

オーバーロードシグネチャの順序

オーバーロードシグネチャは、より具体的なものから順に記述する必要があります。TypeScriptは上から順にシグネチャをマッチングするため、順序が重要です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// より具体的なシグネチャを先に記述
function createElement(tag: "a"): HTMLAnchorElement;
function createElement(tag: "div"): HTMLDivElement;
function createElement(tag: "span"): HTMLSpanElement;
function createElement(tag: string): HTMLElement;
function createElement(tag: string): HTMLElement {
  return document.createElement(tag);
}

// 使用例
const anchor = createElement("a");   // HTMLAnchorElement
const div = createElement("div");    // HTMLDivElement
const custom = createElement("custom-element"); // HTMLElement

異なる引数の数を持つオーバーロード

引数の数が異なるオーバーロードも定義できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 異なる引数の数を持つオーバーロード
function createPoint(x: number, y: number): { x: number; y: number };
function createPoint(coords: [number, number]): { x: number; y: number };
function createPoint(
  xOrCoords: number | [number, number],
  y?: number
): { x: number; y: number } {
  if (Array.isArray(xOrCoords)) {
    return { x: xOrCoords[0], y: xOrCoords[1] };
  }
  return { x: xOrCoords, y: y! };
}

// 使用例
const point1 = createPoint(10, 20);    // { x: 10, y: 20 }
const point2 = createPoint([30, 40]);  // { x: 30, y: 40 }

戻り値の型が異なるオーバーロード

引数の型に応じて戻り値の型が変わるオーバーロードも定義できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 戻り値の型が異なるオーバーロード
function parseValue(value: string, type: "number"): number;
function parseValue(value: string, type: "boolean"): boolean;
function parseValue(value: string, type: "string"): string;
function parseValue(
  value: string,
  type: "number" | "boolean" | "string"
): number | boolean | string {
  switch (type) {
    case "number":
      return parseFloat(value);
    case "boolean":
      return value === "true";
    case "string":
      return value;
  }
}

// 使用例
const num = parseValue("42", "number");     // number型
const bool = parseValue("true", "boolean"); // boolean型
const str = parseValue("hello", "string");  // string型

オーバーロードの注意点

オーバーロードを使う際は、以下の点に注意しましょう。

  • 実装シグネチャは外部から呼び出せない
  • オーバーロードシグネチャは2つ以上必要
  • 実装シグネチャは、すべてのオーバーロードシグネチャを満たす必要がある
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 実装シグネチャは外部から呼び出せない
function process(value: string): string;
function process(value: number): number;
function process(value: string | number): string | number {
  return value;
}

process("hello"); // OK
process(42);      // OK
// process(true);  // Error: 実装シグネチャは呼び出せない

関数型の実践パターン

コールバック関数の型定義

高階関数でコールバックを受け取る場合、コールバック関数の型を適切に定義しましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// コールバック関数の型定義
type Callback<T> = (error: Error | null, result: T | null) => void;

function fetchUser(id: number, callback: Callback<{ name: string; age: number }>): void {
  // 非同期処理のシミュレーション
  setTimeout(() => {
    if (id > 0) {
      callback(null, { name: "Alice", age: 30 });
    } else {
      callback(new Error("Invalid user ID"), null);
    }
  }, 1000);
}

// 使用例
fetchUser(1, (error, result) => {
  if (error) {
    console.error(error.message);
  } else {
    console.log(result); // { name: "Alice", age: 30 }
  }
});

ジェネリック関数

ジェネリクスを使うと、型パラメータを持つ柔軟な関数を定義できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// ジェネリック関数
function identity<T>(value: T): T {
  return value;
}

// 使用例(型推論)
const str = identity("hello"); // string型
const num = identity(42);      // number型

// 明示的な型引数
const arr = identity<number[]>([1, 2, 3]); // number[]型

関数型とオブジェクト型の組み合わせ

関数をプロパティとして持つオブジェクトの型も定義できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 関数をプロパティとして持つオブジェクト型
interface Calculator {
  add: (a: number, b: number) => number;
  subtract: (a: number, b: number) => number;
  multiply: (a: number, b: number) => number;
  divide: (a: number, b: number) => number;
}

const calculator: Calculator = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b,
  divide: (a, b) => a / b,
};

console.log(calculator.add(10, 5));      // 15
console.log(calculator.multiply(10, 5)); // 50

thisの型付け

メソッドとして使用される関数でthisの型を明示的に定義できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// thisの型付け
interface Counter {
  count: number;
  increment(this: Counter): void;
  decrement(this: Counter): void;
  getCount(this: Counter): number;
}

const counter: Counter = {
  count: 0,
  increment() {
    this.count++;
  },
  decrement() {
    this.count--;
  },
  getCount() {
    return this.count;
  },
};

counter.increment();
counter.increment();
console.log(counter.getCount()); // 2

まとめ

本記事では、TypeScriptにおける関数の型定義について解説しました。重要なポイントを振り返ります。

概念 説明
基本的な型注釈 引数と戻り値に型を注釈して型安全性を確保する
void型とnever型 戻り値がない関数と決して戻らない関数を区別する
アロー関数の型定義 変数への代入時に関数型を注釈する
型エイリアス 関数型を再利用可能な形で定義する
オプショナル引数 ?を使って省略可能な引数を定義する
デフォルト引数 省略時のデフォルト値を設定する
残余引数 ...を使って可変長の引数を受け取る
関数オーバーロード 複数のシグネチャを持つ関数を定義する

TypeScriptの関数型を適切に活用することで、より堅牢で保守性の高いコードを書くことができます。次の記事では、コールバック関数と高階関数の型定義についてさらに深掘りしていきます。

参考リンク