こんにちは、かつコーチです。
前回はオブジェクトの型定義について解説しました。
今回は、TypeScriptのもう一つの基本である「関数の型」を扱います。
関数に型をつけられるようになると、「この関数に何を渡せばよくて、何が返ってくるのか」がコードだけで分かるようになります。
呼び出す側も実装する側も安心してコードを書けるようになるので、しっかりマスターしていきましょう。
関数の型とは?
なぜ関数にも型が必要なのか
JavaScriptの関数は、どんな値を渡してもエラーにならず、とりあえず実行されてしまいます。
その結果、文字列を渡すべきところに数値を渡してしまったり、戻り値を勘違いして次の処理でバグを生んだりすることが起こります。
TypeScriptで関数に型をつけると、引数の型と戻り値の型があらかじめ決まるため、間違った値を渡した瞬間にエラーとして検出できます。
「関数のインターフェース(外から見た仕様)」を明示できる、と考えると分かりやすいです。
引数に型をつける基本形
まずは引数に型をつける基本の書き方です。
function greet(name: string): void {
console.log(`こんにちは、${name}さん`);
}
greet("かつコーチ"); // OK
greet(123); // エラー:数値は渡せない
name: string のように、引数名の後ろにコロンと型を書きます。
greet(123) のように違う型の値を渡すと、コンパイル時にエラーになります。
戻り値の型をつける
戻り値に型注釈をつける書き方
引数の後ろの : 型 は、実は戻り値の型を表しています。
function add(a: number, b: number): number {
return a + b;
}
const result = add(3, 5);
console.log(result); // 8
function add(a: number, b: number): number の最後の : number が、この関数が「数値を返す」ことを表しています。
もし return の値が number 型でなければ、コンパイル時にエラーになります。
何も返さない関数:void
値を返さず、処理だけを行う関数には void 型を指定します。
function logMessage(message: string): void {
console.log(message);
// returnで値を返さない
}
void は「戻り値がない」ことを表す特別な型です。
console.log のように画面に表示するだけの関数は、だいたい void になります。
戻り値の型を省略する
実は戻り値の型は省略しても、TypeScriptが return の内容から自動で推論してくれます。
function multiply(a: number, b: number) {
return a * b; // 戻り値の型はnumberと自動推論される
}
ただし、関数の仕様を明確にするために、公開APIや複数人で使う関数では戻り値の型を明示的に書いておくのがおすすめです。
型を明示しておくと、実装を間違えたときにもエラーとして気づけます。
オプション引数とデフォルト引数
? で省略可能な引数にする
引数の一部を省略できるようにするには、? をつけます。
function greet(name: string, honorific?: string): string {
if (honorific) {
return `${name}${honorific}、こんにちは`;
}
return `${name}さん、こんにちは`;
}
console.log(greet("かつコーチ")); // "かつコーチさん、こんにちは"
console.log(greet("かつコーチ", "先生")); // "かつコーチ先生、こんにちは"
honorific?: string と書くことで、honorific を渡さなくても呼び出せるようになります。
オプション引数は、必ず必須引数より後ろに書く必要があるので注意してください。
デフォルト値を設定する
引数を省略したときのデフォルト値を決めておくこともできます。
function greet(name: string, honorific: string = "さん"): string {
return `${name}${honorific}、こんにちは`;
}
console.log(greet("かつコーチ")); // "かつコーチさん、こんにちは"
console.log(greet("かつコーチ", "先生")); // "かつコーチ先生、こんにちは"
honorific: string = "さん" のように書くと、引数を省略したときに自動で "さん" が使われます。
デフォルト値を指定した引数は ? をつけなくても省略可能な引数として扱われます。
アロー関数に型をつける
アロー関数での書き方
アロー関数でも書き方はほぼ同じです。
const add = (a: number, b: number): number => {
return a + b;
};
const double = (n: number): number => n * 2;
引数と戻り値の型注釈のつけ方は、通常の関数宣言と変わりません。
処理が1行で済む場合は、波括弧と return を省略した書き方もよく使われます。
つまずきやすいポイント:any型に逃げてしまう
実際につまずいた話
私が関数に型をつけ始めたばかりのころ、引数の型がすぐに決まらない場面で、つい any 型を使ってしまうことがよくありました。
any はどんな型でも受け入れてしまう特殊な型で、書けばエラーは一時的に消えます。
ですが、ある日「関数の引数にオブジェクトを渡したはずなのに、実行時に undefined のプロパティにアクセスしてエラーになる」という不具合に遭遇しました。
原因を調べたところ、その関数の引数が any になっていたせいで、TypeScriptがそもそも型チェックをしてくれていなかったことに気づきました。
any を使うと、TypeScriptを使っているのに型の恩恵をまったく受けられなくなる、という典型的な失敗パターンです。
Before/Afterで確認する
❌ Before:any型で引数の中身を保証しない
function calculateTotal(items: any): number {
let total = 0;
for (const item of items) {
total += item.price; // itemsやitem.priceの型が保証されないため、実行時エラーの温床になる
}
return total;
}
// 誤った形のデータを渡してもコンパイル時にエラーにならない
calculateTotal([{ name: "商品A" }]); // priceがないためNaNになるが、コンパイルは通ってしまう
items が any 型になっているため、item.price が本当に存在するのかをTypeScriptがチェックしてくれません。
結果として、実行するまでバグに気づけない状態になっています。
✅ After:具体的な型を定義して引数に指定する
type Item = {
name: string;
price: number;
};
function calculateTotal(items: Item[]): number {
let total = 0;
for (const item of items) {
total += item.price;
}
return total;
}
calculateTotal([{ name: "商品A", price: 1000 }]); // OK
calculateTotal([{ name: "商品B" }]); // エラー:priceが不足している
Item[] という具体的な型を指定することで、price が不足したデータを渡した瞬間にコンパイルエラーとして気づけます。
「型が決まらないから any」ではなく、「今分かっている範囲でできるだけ具体的な型を書く」という姿勢が、TypeScriptを使うメリットを最大限に活かすコツです。
応用:関数の型そのものに名前をつける
関数の型をtypeで定義する
コールバック関数を受け取るような場面では、関数自体の型に名前をつけておくと便利です。
type CalculateFn = (a: number, b: number) => number;
const add: CalculateFn = (a, b) => a + b;
const subtract: CalculateFn = (a, b) => a - b;
function runCalculation(a: number, b: number, fn: CalculateFn): number {
return fn(a, b);
}
console.log(runCalculation(10, 5, add)); // 15
console.log(runCalculation(10, 5, subtract)); // 5
type CalculateFn = (a: number, b: number) => number; のように書くと、「引数がnumber二つで戻り値がnumberの関数」という型に名前をつけられます。
CalculateFn 型を使う側では、add や subtract の引数の型を毎回書く必要がなくなり、コードがすっきりします。
まとめ
この記事のポイント
- 関数の引数と戻り値には
引数名: 型と): 戻り値の型で型をつける - 値を返さない関数には
void型を指定する ?やデフォルト値でオプション引数を表現できる- 引数の型を安易に
anyにすると型チェックの恩恵を受けられなくなる - 関数の型そのものに
typeで名前をつけると、コールバック関数の型指定が楽になる
次に読むべき記事
関数の型が扱えるようになったら、次は特殊な型である any・unknown・never の違いを理解していきましょう。
→ 次の記事:any型・unknown型・never型の違い