function-type-expressions について
関数式全体に型を割り当て、注釈の重複を減らし、戻り値を検査するTypeScriptスキルです。コールバックやラッパー、同じシグネチャを持つ関数を書く開発者向けです。
主な機能
- 01関数式全体への型指定
- 02同じシグネチャ間での型の再利用
- 03関数型からの引数型推論
- 04型に基づく戻り値の検査
- 05typeofによる既存シグネチャの照合
活用シーン
- 01ライブラリのコールバックへの型付け
- 02共通シグネチャを持つ関数の実装
- 03引数シグネチャを保った関数のラップ
- 04関連関数間での注釈の重複削減
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill function-type-expressionsインストール後、Claude Code で/function-type-expressionsを入力して読み込まれたか確認します。
詳細
可能な場合は関数式全体に型を適用する
概要
個々のパラメーターではなく、関数全体に一度に型を付けます。
関数式(関数宣言ではありません)を使用する場合、関数全体に型を適用できます。これにより、繰り返しが減り、型安全性が向上し、コードが読みやすくなります。
このスキルを使う場面
- 同じシグネチャを持つ複数の関数を書く場合
- ライブラリのコールバックを実装する場合
- 既存の関数のシグネチャに合わせる場合
- 既存の関数をラップまたは拡張する場合
鉄則
When functions share a signature, define the type ONCE and apply it to EACH function.
覚えておきましょう:
- 関数式には型を適用できます
- 関数型からパラメーターの型が推論されます
- 戻り値の型は関数型と照合されます
- 既存の関数のシグネチャに合わせるには
typeof fnを使います
検出:シグネチャの繰り返し
// Repetitive - same signature 4 times
function add(a: number, b: number) { return a + b; }
function sub(a: number, b: number) { return a - b; }
function mul(a: number, b: number) { return a * b; }
function div(a: number, b: number) { return a / b; }
解決策:関数型
一度定義して、何度も使う
type BinaryFn = (a: number, b: number) => number;
const add: BinaryFn = (a, b) => a + b; // Types inferred
const sub: BinaryFn = (a, b) => a - b;
const mul: BinaryFn = (a, b) => a * b;
const div: BinaryFn = (a, b) => a / b;
利点:
- 型注釈を繰り返す必要がありません
- 戻り値の型が自動的にチェックされます
- 型注釈によるノイズがなくなり、ロジックがより見やすくなります
typeof で既存の関数に合わせる
// Match fetch's signature exactly
const checkedFetch: typeof fetch = async (input, init) => {
const response = await fetch(input, init);
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response;
};
TypeScript は次の型を推論します:
input: RequestInfo | URLinit?: RequestInit- 戻り値:
Promise<Response>
パラメーターとともに戻り値の型を変更する
// Match parameters but change return type
async function fetchNumber(
...args: Parameters<typeof fetch>
): Promise<number> {
const response = await checkedFetch(...args);
return Number(await response.text());
}
関数宣言と関数式
// Statement - must annotate each parameter
function rollDice1(sides: number): number { /* ... */ }
// Expression - can apply type to entire function
type DiceRollFn = (sides: number) => number;
const rollDice2: DiceRollFn = (sides) => { /* ... */ };
よく使われる関数型のパターン
コールバック型
type EventHandler = (event: Event) => void;
type AsyncCallback<T> = () => Promise<T>;
type Comparator<T> = (a: T, b: T) => number;
const handleClick: EventHandler = (e) => {
console.log(e.target); // e is typed as Event
};
ジェネリック関数型
type Mapper<T, U> = (item: T, index: number) => U;
const double: Mapper<number, number> = (n) => n * 2;
const stringify: Mapper<number, string> = (n) => String(n);
インターフェイス構文(別の方法)
// Function type as interface
interface StringTransform {
(input: string): string;
}
const toUpper: StringTransform = s => s.toUpperCase();
戻り値の型安全性
関数型を使うと、戻り値の型エラーを検出できます:
const checkedFetch: typeof fetch = async (input, init) => {
const response = await fetch(input, init);
if (!response.ok) {
return new Error('Failed'); // Error!
// Type 'Error' is not assignable to type 'Response'
}
return response;
};
関数型を使わない場合、呼び出し箇所で初めてエラーになります。
ライブラリのコールバック型
ライブラリでは、コールバック型が提供されていることがよくあります:
// React provides these
import { MouseEventHandler, ChangeEventHandler } from 'react';
const handleClick: MouseEventHandler<HTMLButtonElement> = (e) => {
console.log(e.currentTarget.disabled); // Fully typed
};
const handleChange: ChangeEventHandler<HTMLInputElement> = (e) => {
console.log(e.target.value); // Fully typed
};
関数型を使わない場面
単一の関数に過剰な設計を加えるのは避けましょう:
// Overkill for a single standalone function
type GreetFn = (name: string) => string;
const greet: GreetFn = (name) => `Hello, ${name}`;
// Just use a normal function statement
function greet(name: string): string {
return `Hello, ${name}`;
}
圧力に負けないためのプロトコル
1. 「関数宣言のほうが明確です」
圧力:「パラメーターと並べて型を見るほうが好みです」
**回答:**シグネチャを共有する場合、型を一元化するとノイズが減ります。
**対応:**2つ以上の関数が同じシグネチャを共有する場合は、関数型を使います。
2. 「ライブラリの型がわかりません」
圧力:「ライブラリ内のコールバック型を見つけられません」
回答:typeof existingFunction または Parameters<typeof fn> を使います。
**対応:**typeof で既存のシグネチャに合わせます。
危険信号 — 立ち止まって再検討する
- 複数の関数で同じパラメーター型が繰り返されている
- ラップ対象の関数とシグネチャを一致させるべきラッパー関数がある
- 適切に型付けされていないコールバックがある
よくある正当化(すべて誤り)
| 言い訳 | 実際のところ |
|---|---|
| 「パラメーターに型を付けたほうが明確です」 | 4回繰り返す場合はそうではありません |
| 「関数宣言のほうがシンプルです」 | 型付きの関数式も同じくらい明確です |
「any を使えばいいだけです」 | 型安全性の利点がすべて失われます |
クイックリファレンス
// Define function type
type Transform<T> = (value: T) => T;
// Apply to function expression
const double: Transform<number> = v => v * 2;
// Match existing function
const myFetch: typeof fetch = async (input, init) => { ... };
// Match parameters, change return
function wrapper(...args: Parameters<typeof original>): NewReturn { ... }
要点
シグネチャを共有する場合は、関数式全体に型を適用します。
これにより、繰り返しが減り、型定義が一元化され、戻り値の型エラーを発生箇所で検出できます。既存の関数シグネチャに正確に合わせるには、typeof を使います。
参考資料
Dan Vanderkam 著『Effective TypeScript』の項目 12「可能な場合は関数式全体に型を適用する」に基づいています。