関数の型
関数のパラメータ・戻り値・コールバックの型定義を学びます
このチャプターで学ぶこと
- 関数のパラメータと戻り値に型を付けられる
- オプション引数とデフォルト値を使える
- コールバック関数の型を定義できる
- 関数型の式を書ける
関数の型
前のチャプターまでで、変数やオブジェクトに型を付ける方法を学びました。しかし実際のアプリケーションでは、データを処理する関数が主役です。特に 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 型にはプロパティがない
void と undefined は似ていますが異なります。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);
};
}ヒント:element は HTMLElement、event は string、handler はイベントを受け取って void を返す関数です。戻り値は「引数なし・戻り値なし」の関数です。
発展課題: 上の on 関数で、event に "click" | "submit" | "input" | "change" のようなリテラル型のユニオンを使って、許可するイベント名を制限してみましょう。
まとめ
このチャプターで学んだことをまとめます:
- 関数のパラメータには
: 型を、戻り値には): 型を付ける voidは値を返さない関数の戻り値型?でオプション引数を定義し、デフォルト値で省略時の値を指定できる- rest パラメータ
...args: type[]で可変長引数を受け取れる - 関数型の式
(a: number) => stringで関数の型を定義できる - コールバック関数の型定義は React のイベントハンドラや Props で頻繁に使う
- PHP の
callableと違い、TypeScript ではコールバックの引数と戻り値の型を厳密に定義できる
関数の型付けは React 開発の基盤です。特にイベントハンドラと Props のコールバック関数の型定義は毎日使います。次のチャプターでは、さらに柔軟な型定義を可能にするジェネリクスを学びます。