Chapter 05

関数の型

関数のパラメータ・戻り値・コールバックの型定義を学びます

30 min

このチャプターで学ぶこと

  • 関数のパラメータと戻り値に型を付けられる
  • オプション引数とデフォルト値を使える
  • コールバック関数の型を定義できる
  • 関数型の式を書ける

関数の型

前のチャプターまでで、変数やオブジェクトに型を付ける方法を学びました。しかし実際のアプリケーションでは、データを処理する関数が主役です。特に React では、コンポーネント自体が関数であり、イベントハンドラもコールバック関数です。

このチャプターでは、関数のパラメータ・戻り値・コールバックに型を付ける方法を学びます。

関数宣言の型付け

関数のパラメータと戻り値に型を指定する基本構文です。

// パラメータに型を付け、戻り値の型を指定する
function greet(name: string): string {
  return `こんにちは、${name}さん`;
}

greet("山田太郎"); // OK
greet(42);         // エラー:number は string に割り当てられない

パラメータ名の後に : 型 を書き、引数リストの閉じ括弧の後に : 戻り値の型 を書きます。

// 複数のパラメータと数値の戻り値
function add(a: number, b: number): number {
  return a + b;
}

// オブジェクトを返す関数
function createUser(name: string, email: string): { name: string; email: string; id: number } {
  return {
    name,
    email,
    id: Math.floor(Math.random() * 10000),
  };
}
💡 ヒント

TypeScript は戻り値の型を推論してくれるため、多くの場合は明示的に書かなくても問題ありません。ただし、公開APIや他のメンバーが使う関数では、戻り値の型を明示しておくとドキュメントとしても機能します。

// 戻り値の型を省略しても TypeScript が推論する
function add(a: number, b: number) {
  return a + b; // 戻り値は number と推論される
}

アロー関数の型付け

React ではアロー関数を多用します。型の付け方は関数宣言とほぼ同じです。

// アロー関数の型付け
const greet = (name: string): string => {
  return `こんにちは、${name}さん`;
};

// 1行で書くパターン(return を省略)
const double = (n: number): number => n * 2;

// オブジェクトを返すアロー関数
// 括弧 () で囲まないとブロックと間違えられる
const createTag = (label: string): { label: string; id: number } => ({
  label,
  id: Math.floor(Math.random() * 10000),
});
⚠️ 注意

アロー関数でオブジェクトリテラルを直接返す場合、() で囲む必要があります。囲まないと {} がブロック文として解釈されてしまいます。

// NG:{} がブロック文として解釈される
const createTag = (label: string) => { label, id: 1 };

// OK:() で囲む
const createTag = (label: string) => ({ label, id: 1 });

void 型 ── 戻り値がない関数

値を返さない関数の戻り値の型は void です。

// コンソールに出力するだけで値を返さない
function logMessage(message: string): void {
  console.log(`[LOG] ${message}`);
}

// イベントハンドラのような関数
function handleClick(event: MouseEvent): void {
  console.log("クリックされました");
}

PHPの void 返り値型と同じ概念です。void は「この関数は意味のある値を返さない」ことを明示します。

// void 関数の戻り値を使おうとするとエラー
function doSomething(): void {
  console.log("処理実行");
}

const result = doSomething();
// result の型は void
// result.toString(); // エラー:void 型にはプロパティがない
📝 メモ

voidundefined は似ていますが異なります。void は「戻り値を使うべきでない」という意味で、undefined は「undefinedという値を返す」という意味です。実務では関数の戻り値には void を使うのが一般的です。

オプション引数

? を付けるとパラメータが省略可能になります。

function greet(name: string, greeting?: string): string {
  // greeting は string | undefined
  if (greeting) {
    return `${greeting}${name}さん`;
  }
  return `こんにちは、${name}さん`;
}

greet("山田太郎");              // "こんにちは、山田太郎さん"
greet("山田太郎", "おはよう");   // "おはよう、山田太郎さん"

オプション引数は必須引数の後に書く必要があります。

// エラー:オプション引数の後に必須引数は書けない
function badFunction(name?: string, age: number): void { }

// OK:必須引数が先、オプション引数が後
function goodFunction(age: number, name?: string): void { }

デフォルト値

JavaScriptのデフォルト値構文も使えます。デフォルト値がある場合、型は自動推論されます。

// デフォルト値から型が推論される
function greet(name: string, greeting: string = "こんにちは"): string {
  return `${greeting}${name}さん`;
}

greet("山田太郎");              // "こんにちは、山田太郎さん"
greet("山田太郎", "おはよう");   // "おはよう、山田太郎さん"
💡 ヒント

オプション引数 greeting?: string とデフォルト値 greeting: string = "こんにちは" の違い:

  • オプション引数:呼び出し側が省略すると undefined になる。関数内で undefined チェックが必要。
  • デフォルト値:呼び出し側が省略するとデフォルト値が使われる。undefined チェックが不要。

Reactコンポーネントの Props ではデフォルト値のパターンがよく使われます。

rest パラメータ(残余引数)

... を使って可変長の引数を配列として受け取れます。

// 数値を任意の個数受け取って合計を返す
function sum(...numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0);
}

sum(1, 2, 3);       // 6
sum(10, 20, 30, 40); // 100
sum();               // 0

// 最初の引数は固定、残りは可変
function logWithPrefix(prefix: string, ...messages: string[]): void {
  messages.forEach((msg) => {
    console.log(`[${prefix}] ${msg}`);
  });
}

logWithPrefix("INFO", "サーバー起動", "ポート3000で待ち受け");
// [INFO] サーバー起動
// [INFO] ポート3000で待ち受け

rest パラメータは必ず最後の引数である必要があります。PHPの可変長引数 ...$args と同じ概念です。

関数の型式(Function Type Expressions)

関数そのものの型を定義するには、関数型の式を使います。

// 関数の型を定義
type MathOperation = (a: number, b: number) => number;

// この型に合う関数を代入
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
const multiply: MathOperation = (a, b) => a * b;

// 型に合わない関数はエラー
const concat: MathOperation = (a, b) => `${a}${b}`;
// エラー:string は number に割り当てられない

パラメータの型を (a: number, b: number) で定義し、=> の後に戻り値の型を書きます。

📝 メモ

関数型の式では => を使いますが、これはアロー関数の => とは別物です。型の文脈では「戻り値の型」を表し、値の文脈では「アロー関数の本体」を表します。

// 型の文脈: => の後は戻り値の型
type Greet = (name: string) => string;

// 値の文脈: => の後は関数の本体
const greet: Greet = (name) => `こんにちは、${name}さん`;

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

関数を引数として受け取る関数(高階関数)は、JavaScript/TypeScript で頻繁に使われます。

// コールバック関数の型をインラインで定義
function fetchData(url: string, onSuccess: (data: string) => void, onError: (error: Error) => void): void {
  try {
    // データ取得処理(簡略化)
    const data = "取得したデータ";
    onSuccess(data);
  } catch (e) {
    onError(e as Error);
  }
}

// 使い方
fetchData(
  "/api/users",
  (data) => console.log("成功:", data),
  (error) => console.error("失敗:", error.message)
);

引数が多い場合は、型を別に定義するとスッキリします。

// コールバックの型を別に定義
type SuccessCallback = (data: string) => void;
type ErrorCallback = (error: Error) => void;

function fetchData(url: string, onSuccess: SuccessCallback, onError: ErrorCallback): void {
  // ...
}

React イベントハンドラの型

React では、ボタンクリックやフォーム入力のイベントハンドラをコールバック関数として渡します。これらの型を知っておくことは非常に重要です。

import { useState } from "react";

function LoginForm() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  // フォーム送信のイベントハンドラ
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
    e.preventDefault();
    console.log("送信:", email, password);
  };

  // 入力変更のイベントハンドラ
  const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
    setEmail(e.target.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={handleEmailChange}
      />
      <input
        type="password"
        value={password}
        onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
          setPassword(e.target.value);
        }}
      />
      <button type="submit">ログイン</button>
    </form>
  );
}
💡 ヒント

React のイベントハンドラの型は最初は覚えにくいですが、よく使うものは限られています。

  • クリック: React.MouseEvent<HTMLButtonElement>
  • フォーム送信: React.FormEvent<HTMLFormElement>
  • input 変更: React.ChangeEvent<HTMLInputElement>
  • キーボード: React.KeyboardEvent<HTMLInputElement>

エディタで onChange= と書いてカーソルを合わせれば、期待される型が表示されます。暗記する必要はありません。

Props にコールバック関数を含める

React コンポーネントの Props にコールバック関数を含めるパターンは非常によく使います。

// Props にコールバック関数を含む型定義
interface TodoItemProps {
  id: number;
  title: string;
  completed: boolean;
  onToggle: (id: number) => void;     // 完了/未完了を切り替える
  onDelete: (id: number) => void;     // 削除する
  onEdit?: (id: number, newTitle: string) => void; // 編集する(省略可能)
}

function TodoItem({ id, title, completed, onToggle, onDelete, onEdit }: TodoItemProps) {
  return (
    <div className="todo-item">
      <input
        type="checkbox"
        checked={completed}
        onChange={() => onToggle(id)}
      />
      <span style={{ textDecoration: completed ? "line-through" : "none" }}>
        {title}
      </span>
      {onEdit && (
        <button onClick={() => onEdit(id, "新しいタイトル")}>
          編集
        </button>
      )}
      <button onClick={() => onDelete(id)}>削除</button>
    </div>
  );
}
// 親コンポーネントでの使い方
function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, title: "TypeScriptを学ぶ", completed: false },
    { id: 2, title: "Reactを復習する", completed: true },
  ]);

  // コールバック関数の実装
  const handleToggle = (id: number): void => {
    setTodos((prev) =>
      prev.map((todo) =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  const handleDelete = (id: number): void => {
    setTodos((prev) => prev.filter((todo) => todo.id !== id));
  };

  return (
    <ul>
      {todos.map((todo) => (
        <TodoItem
          key={todo.id}
          id={todo.id}
          title={todo.title}
          completed={todo.completed}
          onToggle={handleToggle}
          onDelete={handleDelete}
        />
      ))}
    </ul>
  );
}
📝 メモ

コールバック Props の命名規則として、on + 動詞(onClick, onSubmit, onDelete)が React コミュニティの慣習です。Laravel のイベントシステム(UserCreated, OrderShipped)とは命名パターンが異なりますが、「何かが起きたときに呼ばれる」という概念は同じです。

PHP の callable との比較

PHPの callable 型ヒントは「何かしらの呼び出し可能なもの」を表しますが、引数や戻り値の型までは制約しません。

// PHP: callable は引数・戻り値の型を制約しない
function processData(array $data, callable $callback): array {
    return array_map($callback, $data);
}

// どんな callable でも渡せてしまう
processData([1, 2, 3], fn($n) => $n * 2);          // OK
processData([1, 2, 3], fn($n) => "hello");          // OK(型安全でない)
processData([1, 2, 3], fn($a, $b) => $a + $b);      // 実行時エラー

TypeScript では、コールバック関数の引数と戻り値の型を厳密に指定できます。

// TypeScript: コールバックの型を厳密に定義
function processData(data: number[], callback: (n: number) => number): number[] {
  return data.map(callback);
}

processData([1, 2, 3], (n) => n * 2);         // OK:number を返す
processData([1, 2, 3], (n) => "hello");        // エラー:string は number に割り当てられない
processData([1, 2, 3], (a, b) => a + b);       // エラー:引数の数が合わない

このように TypeScript では、関数の「形」を型レベルで厳密にチェックできます。

関数オーバーロード(発展)

同じ関数名で異なる引数パターンを持つ関数を定義できます。

// オーバーロードシグネチャ
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.trim();
  }
  if (typeof value === "number") {
    return value.toLocaleString("ja-JP");
  }
  return value.toLocaleDateString("ja-JP");
}

format("  hello  "); // "hello"
format(12345);        // "12,345"
format(new Date());   // "2025/1/15"
⚠️ 注意

関数オーバーロードは複雑になりがちです。多くの場合、ユニオン型やジェネリクス(後のチャプターで扱います)で代替できます。実務では「本当にオーバーロードが必要か?」を考えてから使いましょう。

実用例:ユーティリティ関数ライブラリ

ここまでの知識を組み合わせて、実用的なユーティリティ関数の型定義を見てみましょう。

// 文字列を整形する関数
function truncate(text: string, maxLength: number, suffix: string = "..."): string {
  if (text.length <= maxLength) {
    return text;
  }
  return text.slice(0, maxLength - suffix.length) + suffix;
}

// 配列をチャンク(小分け)にする関数
function chunk<T>(array: T[], size: number): T[][] {
  const chunks: T[][] = [];
  for (let i = 0; i < array.length; i += size) {
    chunks.push(array.slice(i, i + size));
  }
  return chunks;
}

// デバウンス(連続呼び出しを制御する関数)
function debounce(fn: (...args: unknown[]) => void, delay: number): (...args: unknown[]) => void {
  let timeoutId: ReturnType<typeof setTimeout>;
  return (...args: unknown[]) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
}
📝 メモ

chunk 関数の <T>ジェネリクス(型パラメータ)です。「どんな型の配列でも受け取れる」ことを表します。ジェネリクスは後のチャプターで詳しく扱いますので、今は「柔軟な型定義の仕組みがある」ということだけ覚えておいてください。

✍ やってみよう:ユーティリティ関数に型を付ける

以下のJavaScript関数にTypeScriptの型を付けてください。

1. 価格表示関数

// 数値を日本円のフォーマットに変換する
// formatPrice(1980) => "¥1,980"
// formatPrice(1980, true) => "¥1,980(税込)"
function formatPrice(price, includeTax) {
  const formatted = `¥${price.toLocaleString("ja-JP")}`;
  if (includeTax) {
    return `${formatted}(税込)`;
  }
  return formatted;
}

ヒント:includeTax はオプション引数にし、デフォルト値を false にしましょう。

2. 配列フィルタ関数

// 条件に合う要素だけを抽出する
// filterBy([{age: 20}, {age: 30}], item => item.age >= 25) => [{age: 30}]
function filterBy(items, predicate) {
  return items.filter(predicate);
}

ヒント:items は特定の型の配列、predicate は各要素を受け取って boolean を返すコールバック関数です。

3. イベントハンドラの登録関数

// DOM 要素にイベントハンドラを登録し、解除関数を返す
function on(element, event, handler) {
  element.addEventListener(event, handler);
  return () => {
    element.removeEventListener(event, handler);
  };
}

ヒント:elementHTMLElementeventstringhandler はイベントを受け取って void を返す関数です。戻り値は「引数なし・戻り値なし」の関数です。

発展課題: 上の on 関数で、event"click" | "submit" | "input" | "change" のようなリテラル型のユニオンを使って、許可するイベント名を制限してみましょう。

まとめ

このチャプターで学んだことをまとめます:

関数の型付けは React 開発の基盤です。特にイベントハンドラと Props のコールバック関数の型定義は毎日使います。次のチャプターでは、さらに柔軟な型定義を可能にするジェネリクスを学びます。