TypeScript:関数の型
1学習の目的
- 関数の引数と戻り値に型を付けられるようになる。関数の「使い方」そのものが型として表現され、呼び出し側の間違いを防げるようになる。
- return の書き忘れが実行前にエラーになることを体験する。JS08で undefined が返ってきて悩んだ問題が、TypeScriptでは起こらなくなる。
2基礎解説
| やりたいこと | 書き方 | 意味 |
|---|---|---|
| 引数と戻り値に型 | function f(a: number): string | 数値を受け取り文字列を返す |
| アロー関数 | const f = (a: number): string => … | 書き方が違うだけ |
| 戻り値なし | function f(): void | 何も返さない |
| 省略可能な引数 | function f(a: number, b?: string) | 必須引数より後ろに置く |
- 戻り値の型は引数の丸カッコの直後に書く。function add(a: number, b: number): number の最後の : number が戻り値の型。
- return を書き忘れると実行前にエラーになる。JS08で「なぜか undefined が返る」と悩んだ問題が、TypeScriptでは書いた時点で分かる。
- 引数の数と型が合わないと呼び出せない。f(1) と f(1, 2) のどちらが正しいかを、型が教えてくれる。
- ? を付けると省略可能な引数になる。必須の引数より後ろに置く必要があり、順序を間違えるとエラーになる。
- 何も返さない関数の戻り値の型は void。void を返す関数の結果を変数に入れようとするとエラーになるので、うっかり使うことも防げる。
現場使用例:金額計算、データの整形、入力値の検証、APIの呼び出し。関数の型は「この関数の使い方」を説明するドキュメントにもなる。
// 基本形:引数と戻り値に型を付ける
function add(a: number, b: number): number {
return a + b;
}
// アロー関数でも同じ
const multiply = (a: number, b: number): number => a * b;
// ❌ return を書き忘れるとエラー
function broken(): number {
console.log("計算中");
}
// → A function whose declared type is neither 'undefined',
// 'void', nor 'any' must return a value.
// 何も返さない関数は void
function log(message: string): void {
console.log(message);
}
// 省略可能な引数(必須引数より後ろに置く)
function greet(name: string, title?: string): string {
return title ? `${title} ${name}` : name;
}
console.log(greet("田中")); // 田中
console.log(greet("田中", "様")); // 様 田中
// 既定値のある引数
function withTax(price: number, rate: number = 0.1): number {
return Math.round(price * (1 + rate));
}
console.log(withTax(1000)); // 1100
console.log(withTax(1000, 0.08)); // 1080
// オブジェクトや配列も引数にできる
function format(item: { name: string; price: number }): string {
return `${item.name}:${item.price.toLocaleString()}円`;
}
3基本ドリル(10問)
出力 == 10
function add(a: number, b: number): number { return a + b; } と書く。
function add(a: number, b: number): number {
return a + b;
}
console.log(add(3, 7));
引数の型は名前のあと、戻り値の型は丸カッコのあと。この2箇所を書くだけで、関数の使い方が型として固定される。
A. undefined が返る B. 実行前にエラーになる C. 0 が返る D. 問題ない 選択★☆☆無料
解答 == B
戻り値の型を number と宣言している。
B
JS08で「なぜか undefined が返る」と悩んだ問題が、書いた時点で分かる。「number を返すと宣言したのに返していない」とTypeScriptが指摘してくれる。
出力 == 42
const double = (n: number): number => n * 2; と書く。
const double = (n: number): number => n * 2; console.log(double(21));
アロー関数でも型の付け方は同じ。1行で書ける場合は return と波カッコを省略できる(JS08)。
function showMessage(text: string): ____ {
console.log(text);
}
showMessage("完了しました");
出力 == 完了しました
「何も返さない」を表す型。小文字で4文字。
void
void は「戻り値が無い」ことを明示する型。書かなくても推論されるが、明示すると「この関数は値を返さない」という意図が伝わる。
A. 2つ目は undefined になる B. 実行前にエラーになる C. 0 が入る D. 問題ない 選択★☆☆無料
解答 == B
引数の数も型チェックの対象になる。
B
Expected 2 arguments, but got 1 というエラーになる。JavaScriptなら undefined のまま計算されて NaN になっていた場面。
出力2行。1100 / 1080
既定値は rate: number = 0.1 のように書く。
function withTax(price: number, rate: number = 0.1): number {
return Math.round(price * (1 + rate));
}
console.log(withTax(1000));
console.log(withTax(1000, 0.08));
既定値を書くと型注釈は省略できるが、明示したほうが読みやすい。よく使う値を既定値にしておくと、呼び出し側が簡潔になる。
function greet(name: string, title____: string): string {
return title ? `${title} ${name}` : name;
}
console.log(greet("田中"));
出力 == 田中
「あってもなくてもよい」を表す1文字の記号。
?
省略可能な引数は必須引数より後ろに置く。順序を逆にすると「必須の引数は省略可能な引数の後には置けない」というエラーになる。
A. 問題なく動く B. エラーになる(省略可能な引数の後に必須引数は置けない) C. b も省略可能になる D. a が必須になる 選択★★☆無料
解答 == B
省略可能な引数を先に書くと、呼び出し側はどう指定すればよいか。
B
もし許されたら f(5) の5がどちらの引数か分からなくなる。だから省略可能な引数は必ず後ろに置く決まりになっている。
出力 == ノートPC:128,000円
引数の型は { name: string; price: number } と書く(TS03)。
function format(item: { name: string; price: number }): string {
return `${item.name}:${item.price.toLocaleString()}円`;
}
console.log(format({ name: "ノートPC", price: 128000 }));
オブジェクトを引数に取る関数は実務で頻出。型を書いておくと、呼び出し側が渡すべきプロパティが明確になる。
出力 == 60
引数の型は number[]、戻り値は number。reduce を使う(JS09)。
function sum(nums: number[]): number {
return nums.reduce((total, n) => total + n, 0);
}
console.log(sum([10, 20, 30]));
配列を受け取り、1つの値を返すという形は集計処理の基本。reduce の中の引数の型は、配列の型から自動的に推論される。
4実践シナリオ(5問)
出力2行が完全一致
三項演算子で判定する。出力は for...of で2件を回すか、2回書いてもよい。
function shippingFee(price: number): number {
return price >= 5000 ? 0 : 600;
}
for (const price of [4800, 12000]) {
console.log(`${price.toLocaleString()}円 → 送料${shippingFee(price)}円`);
}
1つのルールを1つの関数に閉じ込める。送料の基準が変わっても、この関数だけを直せばよい(JS08と同じ考え方)。
出力2行。18000 / 20000
isMember: boolean = false のように既定値を付ける。
function memberPrice(price: number, isMember: boolean = false): number {
return isMember ? Math.round(price * 0.9) : price;
}
console.log(memberPrice(20000, true));
console.log(memberPrice(20000));
既定値を「安全な側」に設定するのがコツ。会員フラグを渡し忘れても、うっかり割引されることはない。
出力 == ノートPC(128,000円), マウス(3,200円), キーボード(8,500円)
引数は { name: string; price: number }[]、戻り値は string[]。map を使う。
function formatAll(items: { name: string; price: number }[]): string[] {
return items.map((i) => `${i.name}(${i.price.toLocaleString()}円)`);
}
const products = [
{ name: "ノートPC", price: 128000 },
{ name: "マウス", price: 3200 },
{ name: "キーボード", price: 8500 },
];
console.log(formatAll(products).join(", "));
配列を受け取り、別の型の配列を返す。{...}[] から string[] へ型が変わることが、戻り値の型注釈から読み取れる。
出力3行。OK / 名前が未入力です / 名前が長すぎます
trim() で前後の空白を除いてから判定する。21文字は "あ".repeat(21) で作れる。
function validateName(name: string): string {
const trimmed: string = name.trim();
if (trimmed.length === 0) {
return "名前が未入力です";
}
if (trimmed.length > 20) {
return "名前が長すぎます";
}
return "OK";
}
console.log(validateName("ノートPC"));
console.log(validateName(""));
console.log(validateName("あ".repeat(21)));
すべての分岐で return を書く必要がある。1つでも return を忘れた経路があると、TypeScriptが「値を返さない場合がある」と指摘してくれる。
出力2行。[10,20,30] / [1,4,9] の形式
関数の型は (n: number) => number と書く。applyAll(nums: number[], fn: (n: number) => number): number[] の形。
function applyAll(
nums: number[],
fn: (n: number) => number
): number[] {
return nums.map(fn);
}
console.log(applyAll([1, 2, 3], (n) => n * 10).join(","));
console.log(applyAll([1, 2, 3], (n) => n ** 2).join(","));
関数そのものにも型が付けられる。(n: number) => number は「数値を受け取って数値を返す関数」という型。JS09の高階関数が、型で守られるようになった。
5仕上げ課題
作る関数
① subtotal(price, qty) … 小計を返す
② memberDiscount(amount, isMember) … 会員なら10%引きの割引額を返す(四捨五入)。isMember は省略時 false
③ shippingFee(amount) … 5,000円以上なら0、未満なら600を返す
④ withTax(amount) … 10%の消費税を加えた金額を返す(四捨五入)
⑤ yen(n) … 数値を「128,000円」の形式の文字列にして返す
処理:単価8500円 × 数量3、会員(true)で計算し、下記を出力する。
期待される出力(5行)
小計:25,500円
会員割引:-2,550円
送料:0円
消費税:2,295円
合計:25,245円
出力5行が完全一致
5つの関数を定義してから、順に呼び出して計算する。小計25500 → 割引2550 → 割引後22950 → 送料0 → 税2295 → 合計25245。yen() は toLocaleString() を使う。
function subtotal(price: number, qty: number): number {
return price * qty;
}
function memberDiscount(amount: number, isMember: boolean = false): number {
return isMember ? Math.round(amount * 0.1) : 0;
}
function shippingFee(amount: number): number {
return amount >= 5000 ? 0 : 600;
}
function withTax(amount: number): number {
return Math.round(amount * 1.1);
}
function yen(n: number): string {
return `${n.toLocaleString()}円`;
}
const price: number = 8500;
const qty: number = 3;
const isMember: boolean = true;
const sub: number = subtotal(price, qty);
const discount: number = memberDiscount(sub, isMember);
const afterDiscount: number = sub - discount;
const shipping: number = shippingFee(afterDiscount);
const taxed: number = withTax(afterDiscount + shipping);
const tax: number = taxed - (afterDiscount + shipping);
console.log(`小計:${yen(sub)}`);
console.log(`会員割引:-${yen(discount)}`);
console.log(`送料:${yen(shipping)}`);
console.log(`消費税:${yen(tax)}`);
console.log(`合計:${yen(taxed)}`);
JS08で書いたのと同じ請求書計算だが、決定的な違いがある。それぞれの関数が「何を受け取り、何を返すか」が型として宣言されているため、使い方を間違えると呼び出した時点でエラーになる。
たとえば shippingFee("25500") と文字列を渡そうとすれば止められるし、const s: string = subtotal(8500, 3); と戻り値を取り違えても止められる。JavaScriptならどちらも実行できてしまい、NaN や "25500円円" のような結果が請求書に印字されて初めて気づくことになる。
さらに memberDiscount の isMember: boolean = false という既定値にも意味がある。渡し忘れたときに「割引しない」側に倒れるため、うっかり全員が割引されるような事故が起きない。既定値は安全な側に設定する——これは型とは別の、設計上の判断だ。
関数に型を付けることは、その関数の「取扱説明書」をコードに埋め込むことでもある。半年後の自分や他の開発者は、関数の中身を読まなくても使い方が分かる。
次章では型推論を学ぶ。実は、ここまで書いてきた型注釈の多くは省略できる——どこを書き、どこを省くかの判断を身につける。