TypeScriptの基礎 - 関数の型定義

提供: MochiuWiki : SUSE, EC, PCB

📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

概要

TypeScriptにおける関数の型定義とは、関数のパラメータと戻り値に対して型を明示する仕組みである。
JavaScriptの関数と比べ、TypeScriptの型情報によってコンパイル時に誤った引数の渡し方や戻り値の誤用を検出できる。

  • 関数の型注釈
    関数宣言、関数式、アロー関数におけるパラメータと戻り値の型注釈の記述方法
    void (値を返さない) と never (終端に到達しない) の使い分け
  • パラメータの柔軟な定義
    オプショナル引数 (?)、デフォルト引数 (= 値)、レストパラメータ (...args) による柔軟な関数シグネチャ
    TypeScript 4.0以降ではタプル型をレストパラメータに使用でき、引数の数と各位置の型を厳密に指定できる
  • 関数のオーバーロード
    同名の関数に複数の型シグネチャを定義し、引数の型に応じた異なる戻り値型を提供する
  • コールバック関数と関数型
    インラインまたは type / interface を用いたコールバック関数の型定義パターン
    コールバックの戻り値型には any ではなく void の使用が推奨される


TypeScript 4.9で導入された satisfies 演算子を使用すると、関数を含むオブジェクトが特定の型を満たすことを検証しつつ、具体的な型情報を保持できる。
TypeScript 5.4の NoInfer ユーティリティ型により、ジェネリック関数においてどのパラメータから型を推論するかを明示的に制御できるようになった。

2026年2月時点でTypeScript 5.9が安定版としてリリースされている。
TypeScript 6.0ベータでは strict: truemodule: "esnext" のデフォルト化等の変更が行われており、Go言語で書き直されるTypeScript 7.0では最大10倍のコンパイル速度向上が見込まれている。


関数の型注釈

TypeScriptでは、関数のパラメータと戻り値に対して型注釈を記述できる。

パラメータの型注釈はパラメータ名の直後にコロンと型名を記述し、戻り値の型注釈はパラメータリストの閉じ括弧の後に記述する。

関数宣言

function キーワードを使用した関数宣言では、パラメータと戻り値にそれぞれ型注釈を付与する。

基本的な構文は以下の通りである。

 function <関数名>(<パラメータ>: <>): <戻り値の型> {
    // 処理
 }


具体的な例を以下に示す。

 function greet(name: string): void {
    console.log("Hello, " + name.toUpperCase() + "!!");
 }
 
 function add(a: number, b: number): number {
    return a + b;
 }
 
 function getFavoriteNumber(): number {
    return 26;
 }


TypeScriptはreturn文から戻り値の型を推論できるため、戻り値の型注釈を省略しても型安全性は保たれる場合がある。

ただし、公開APIや複雑な関数では、意図を明示し意図しない変更を防ぐために戻り値の型注釈を記述することが推奨される。

関数式とアロー関数

関数式やアロー関数においても、パラメータと戻り値に型注釈を付与できる。

型注釈の記述方法には、変数に対して型を付与するパターンと、関数本体に対して型を付与するパターンの2種類がある。

関数式において変数に型注釈を付与する場合は以下の通りである。

 // パターン1 : 変数に型注釈を付与する
 let fn: (param1: string, param2: number) => string = function(param1, param2) {
    return param1 + param2;
 };
 
 // パターン2 : 関数本体に型注釈を付与する
 let fn2 = function(param1: string, param2: number): string {
    return param1 + param2;
 };


アロー関数における型注釈の例を以下に示す。

 const add = (a: number, b: number): number => {
    return a + b;
 };
 
 const callback: (value: string) => void = (value) => {
    console.log(value);
 };


パターン1の記述では、変数に型を付与することで型情報とロジックが分離される。

パターン2の記述では、実装と型注釈が近い位置に記述されるため、ソースコードの可読性が高まる場合がある。

voidとnever

TypeScriptには、関数の戻り値として特別な意味を持つ void 型と never 型がある。
これらは似ているようで異なる用途を持つ。

void型

void 型は、関数が値を返さないことを表す戻り値型である。
戻り値が不要な関数やコールバック関数の戻り値を意図的に無視する場合に使用する。

 function printValue(): void {
    console.log("Hello");
 }


コールバック関数の戻り値の型に void を使用する場合、コールバックがどのような値を返しても型エラーにならない。
any を使用すると戻り値が誤用される実行時エラーが発生する可能性があるため、コールバックの戻り値型には void を使用することが推奨される。

 // コールバックの戻り値を無視する場合はvoidを使用する
 function fn(x: () => void) {
    x();
 }


never型

never 型は、関数が正常に値を返すことがない場合の戻り値型である。
関数の終端に到達しないことを型レベルで示す。

下表に、never 型が使用される主なケースを示す。

never型の主な使用場面
場面 説明
例外をスローする関数 必ず、throw を実行するため、return文に到達しない。
無限ループを含む関数 ループが終了しないため、関数の終端に到達しない。
到達不可能なコードパスの検出
(exhaustive checking)
switch文のdefault節に never 型の変数を置くことにより、
型の網羅性をコンパイル時に検証する。


 function error(message: string): never {
    throw new Error(message);
 }
 
 function infiniteLoop(): never {
    while (true) {}
 }


exhaustive checkingの活用例を以下に示す。
Shape 型に新しいケースが追加された時、default 節で never 型への代入がコンパイルエラーとなり、処理の追加漏れを確実に検出できる。

 type Shape = Circle | Square;
 
 function getArea(shape: Shape) {
    switch (shape.kind) {
       case "circle":
          return Math.PI * shape.radius ** 2;
       case "square":
          return shape.sideLength ** 2;
       default:
          const _exhaustiveCheck: never = shape;
          return _exhaustiveCheck;
    }
 }


下表に、voidnever の違いを示す。

void と never の比較
特性 void never
関数の性質 値を返さない (正常終了) 終端に到達しない。
戻り値 undefined 値を返す文がない。
使用シーン 通常の処理完了後に値が不要 例外送出または無限ループ



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

TypeScriptでは、パラメータを省略可能にしたり、省略時のデフォルト値を設定したりできる。

オプショナル引数

パラメータ名の直後に ? を付与することにより、呼び出し時にそのパラメータを省略できる。
省略した場合、そのパラメータの値は undefined になる。

オプショナル引数は、必須引数の後に配置する必要がある。

 function greet(name: string, greeting?: string): string {
    return (greeting || "Hello") + ", " + name;
 }
 
 greet("Alice");        // OK : greetingはundefined
 greet("Alice", "Hi");  // OK : greetingは"Hi"


関数内でオプショナル引数を使用する場合は、undefined になる可能性を考慮して処理を記述する。

デフォルト引数

パラメータに = 値 の形式でデフォルト値を設定できる。

呼び出し時にそのパラメータを省略すると、デフォルト値が使用される。
デフォルト値が指定されている場合、TypeScriptは値から型を推論するため、型注釈を省略できる。

 function greet(name: string, greeting: string = "Hello"): string {
    return greeting + ", " + name;
 }
 
 greet("Alice");        // "Hello, Alice"
 greet("Alice", "Hi");  // "Hi, Alice"


分割代入 (Destructuring) の引数にもデフォルト値を設定できる。

 type PaintOptions = {
    shape: string;
    xPos?: number;
    yPos?: number;
 };
 
 function paintShape({ shape, xPos = 0, yPos = 0 }: PaintOptions) {
    console.log("x coordinate at", xPos);
    console.log("y coordinate at", yPos);
 }



レストパラメータ

レストパラメータを使用すると、可変長の引数を配列として受け取ることができる。

パラメータ名の前に ... を付与し、型には配列型を指定する。
レストパラメータは、関数のパラメータリストの最後に配置する必要がある。

 function sum(...numbers: number[]): number {
    return numbers.reduce((a, b) => a + b, 0);
 }
 
 sum(1, 2, 3);        // 6
 sum(1, 2, 3, 4, 5);  // 15


TypeScript 4.0以降では、タプル型をレストパラメータに使用できる。
タプル型を使用することにより、引数の数と各位置の型を厳密に指定できる。

 function foo(...args: [string, number]): void {
    const [name, age] = args;
    console.log(`${name} is ${age} years old`);
 }
 
 foo("Alice", 30);  // OK
 foo("Alice");      // エラー : Expected 2 arguments, but got 1.



関数のオーバーロード

TypeScriptの関数オーバーロードは、同名の関数に複数の型シグネチャを定義する機能である。
呼び出し元に対して、引数の型に応じた異なる戻り値の型を提供できる。

関数オーバーロードは、オーバーロードシグネチャ実装シグネチャ の2つで構成される。

関数オーバーロードのシグネチャの種類
シグネチャ 説明
オーバーロードシグネチャ 呼び出し側から見える型定義
関数本体 (実装) を持たない。
実装シグネチャ 実際の処理を含む関数定義
外部から直接呼び出すことはできない。


 // オーバーロードシグネチャ
 function printValue(str: string): void;
 function printValue(num: number, maxFractionDigits?: number): void;
 
 // 実装シグネチャ
 function printValue(value: string | number, maximumFractionDigits?: number) {
    if (typeof value === "number") {
       const formatter = Intl.NumberFormat("en-US", {
          maximumFractionDigits,
       });
       value = formatter.format(value);
    }
    console.log(value);
 }
 
 printValue("Hello");     // OK
 printValue(3.14159, 2);  // OK


関数オーバーロードを記述する際の重要なルールを以下に示す。

  • オーバーロードシグネチャは、より具体的な型から一般的な型の順に記述する。
  • 実装シグネチャは全てのオーバーロードシグネチャと互換性を持つ必要がある。
  • 実装シグネチャは外部から直接呼び出すことができない。


オーバーロードを使用する場面が複雑になる場合は、ユニオン型を使用した単一の関数定義で代替できることも多い。


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

TypeScriptでは、コールバック関数の型を明示的に定義することにより、コールバックの引数と戻り値の型安全性を確保できる。

インラインでの型定義

コールバックを受け取るパラメータに対して、直接型を記述する方法である。

 function processData(callback: (value: string) => void) {
    const result = "processed";
    callback(result);
 }


型エイリアスを使用した型定義

type キーワードでコールバック関数の型に名前を付けることにより、再利用性を高め、ソースコードの可読性を向上させることができる。

 type StringCallback = (value: string) => void;
 
 function processData(callback: StringCallback) {
    const result = "processed";
    callback(result);
 }


複数のパラメータを持つコールバックの型定義例を以下に示す。

 type EventCallback = (event: Event, context: string) => boolean;
 
 function on(event: string, callback: EventCallback): void {
    // イベント登録の処理
 }


コールバック関数の戻り値の型には void を使用することにより、コールバックがどのような値を返してもエラーにならない。

any を使用すると戻り値が予期しない形で使用されるリスクがあるため、void の使用が推奨される。


関数型の定義

TypeScriptでは、type キーワード や interface キーワードを使用して、関数の型を独立して定義できる。

型に名前を付けることにより、複数の箇所で同じ関数型を再利用できる。

typeによる定義

type キーワードを使用した関数型の定義は直感的で、関数型を表現する時に広く使用される方法である。

 type Callback = (value: string) => void;
 type Add = (a: number, b: number) => number;
 type AsyncOperation = (data: unknown) => Promise<void>;
 
 const add: Add = (a, b) => a + b;


ジェネリック型と組み合わせることにより、汎用的な関数型を定義できる。

 type Mapper<T, U> = (input: T) => U;
 type Filter<T> = (item: T) => boolean;
 
 const toLength: Mapper<string, number> = (s) => s.length;
 const isEven: Filter<number> = (n) => n % 2 === 0;


async関数は常に Promise 型を返すため、戻り値の型に Promise<T> を指定する。

 async function fetchData(): Promise<string> {
    const response = await fetch("/api/data");
    return response.text();
 }
 
 const asyncArrow = async (): Promise<number> => {
    return 42;
 };


interfaceによる定義

interface キーワードを使用して関数型を定義する場合は、call signatureを用いる構文を使用する。

 interface StringCallback {
    (value: string): void;
 }
 
 const myCallback: StringCallback = (value) => {
    console.log(value);
 };


ジェネリックインターフェースとして定義することもできる。

 interface Mapper<T, U> {
    (input: T): U;
 }
 
 const stringify: Mapper<number, string> = (n) => String(n);


関数型の定義には type を使用する方が構文的に直感的である。

interface はオブジェクトの構造定義に使用するのが適切であり、関数型の定義には type を優先することが推奨される。

typeinterface の詳細な使い分けについては、
TypeScriptの基礎 - 型エイリアス および TypeScriptの基礎 - オブジェクト型とインターフェースのページを参照すること。


関連情報