はじめに:型エラーは「怒られている」のではなく「守られている」
関数まわりで出る型エラーは、最初かなりストレスになりますよね。
でも本質的には、「その書き方だと、実行時にバグる可能性が高いよ」と TypeScript が先に教えてくれているだけです。
ここでは、関数で初心者がほぼ必ず踏む代表的な型エラーを、
状況別にかみ砕いて解説していきます。
「エラー文を見たとき、何を読み取ればいいか」
「どう直せばいいか/どう考えればいいか」
にフォーカスして話します。
パターン1:引数の数が合わないエラー
Expected n arguments, but got m.
一番よく見るエラーの1つです。
function add(a: number, b: number) {
return a + b;
}
add(1); // エラー: Expected 2 arguments, but got 1.
add(1, 2, 3); // エラー: Expected 2 arguments, but got 3.
TypeScriptTypeScript は、「関数が宣言している引数の数」と「実際に渡している数」が違うと止めます。
JavaScript だと、
- 足りない引数 →
undefinedが勝手に入る - 多すぎる引数 → 余ったぶんは無視
という挙動をしますが、それがバグの温床なので、TypeScript は厳しくチェックします。
考え方はシンプルで、
「この関数は、何個の引数をもらう設計なんだっけ?」
「呼び出し側は、その設計どおりに渡せているか?」
を確認するだけです。
もし「2個目の引数は省略可能にしたい」なら、オプション引数かデフォルト引数を使うべきです。
function add(a: number, b?: number) {
return b !== undefined ? a + b : a;
}
add(1); // OK
add(1, 2); // OK
TypeScriptパターン2:引数の型が合わないエラー
Argument of type ‘X’ is not assignable to parameter of type ‘Y’.
次に多いのがこれです。
function greet(name: string) {
console.log(`Hello, ${name}`);
}
greet("Taro"); // OK
greet(123); // エラー
// Argument of type 'number' is not assignable to parameter of type 'string'.
TypeScriptこのエラーが言っているのは、
「この関数は string を受け取ると宣言しているのに、number を渡そうとしてるよ」
ということです。
落ち着いて、
関数の定義側の型(ここでは name: string)
呼び出し側で渡している実際の値の型(ここでは 123 → number)
を見比べてみてください。
特によくあるのが、string | null や string | undefined をそのまま渡してしまうケースです。
function toUpper(value: string) {
return value.toUpperCase();
}
let maybeName: string | null = null;
toUpper(maybeName);
// エラー: Argument of type 'string | null' is not assignable to parameter of type 'string'.
TypeScriptここでは、
「toUpper は“null の可能性を考えていない関数”」
「でも、maybeName は null になるかもしれない」
というギャップがエラーになっています。
この場合は、
null の場合を先に処理する
デフォルト値を入れて string にしてから渡す
そもそも toUpper の引数型を string | null に変える
など、設計として「null をどう扱うか」を決める必要があります。
パターン3:戻り値の型が合わないエラー
Type ‘X’ is not assignable to type ‘Y’(returnの部分)
戻り値型を明示したときによく出るエラーです。
function add(a: number, b: number): number {
return a + b;
}
TypeScriptここで、うっかり違う型を返すとこうなります。
function add(a: number, b: number): number {
return `${a + b}`;
// ~~~~~~~~~~~~~~~
// Type 'string' is not assignable to type 'number'.
}
TypeScript意味としては、
「この関数は number を返すと約束しているのに、string を返そうとしている」
ということです。
このエラーが出たときに考えるべきなのは2つだけです。
自分が本当に欲しい関数は、「number を返す関数」なのか、「string を返す関数」なのか
その意図に合わせて、戻り値か戻り値型のどちらを直すべきか
戻り値型をちゃんと書いておくと、実装を変えたときに「約束」を勝手に変えないように TypeScript が守ってくれるので、むしろありがたいエラーだと考えてほしいです。
パターン4:戻り値が抜けているエラー(特にアロー関数)
Type ‘void’ is not assignable to type ‘X’.
関数型を変数につけているときによく出ます。
type Add = (a: number, b: number) => number;
const add: Add = (a, b) => {
a + b; // return を書き忘れた
};
// Type 'void' is not assignable to type 'number'.
TypeScriptアロー関数で波かっこ {} を使った場合、return を書き忘れると戻り値は void(何も返さない)扱いになります。
でも型のほうでは「number を返す関数」だと宣言しているので、
「実装は void(何も返してない)
でも約束では number を返すべき」
というギャップでエラーになります。
修正はシンプルで、return を付けるか、ブロックを外して即時返す形にすることです。
const add: Add = (a, b) => {
return a + b;
};
// または
const add2: Add = (a, b) => a + b;
TypeScriptこのエラーが出たときは、
「この関数は本当に何か返したい関数だったのか」
「それとも副作用だけの関数(戻り値いらない)だったのか」
を一度立ち止まって確認してみると、設計の整理にもなります。
パターン5:オブジェクト引数で「プロパティが足りない/余計」なエラー
Property ‘x’ is missing in type ‘…’ but required in type ‘…’.
オブジェクトを引数に取る関数でよく出ます。
type User = {
name: string;
age: number;
};
function printUser(user: User) {
console.log(user.name, user.age);
}
printUser({ name: "Taro" });
// エラー: Property 'age' is missing in type '{ name: string; }' but required in type 'User'.
TypeScript意味としては、
「User 型には age が required(必須)なのに、渡しているオブジェクトには age がない」
ということです。
逆に、余計なプロパティを渡して怒られることもあります。(厳密なオブジェクトリテラルチェック)
printUser({ name: "Taro", age: 20, email: "a@example.com" });
// エラー: Object literal may only specify known properties, and 'email' does not exist in type 'User'.
TypeScriptここで TypeScript が守ってくれているのは、
「printUser は、“User 型のオブジェクト”を引数に取る関数」
「User 型の定義と違う形のオブジェクトが来たら危ない」
という約束です。
この手のエラーに出会ったら、
関数が期待している型(User 型など)の定義
実際に渡そうとしているオブジェクトの形
を並べて見比べてみるクセをつけてください。
「どのプロパティが足りない/余計か」がだんだん一瞬で読めるようになります。
パターン6:コールバックの引数型が合わないエラー
Parameter ‘x’ implicitly has an ‘any’ type や、引数型不一致
配列メソッドや自作の高階関数でよく起きます。
const numbers = [1, 2, 3];
numbers.map((n) => n.toUpperCase());
// ~
// Property 'toUpperCase' does not exist on type 'number'.
TypeScriptここでは numbers が number[] なので、map のコールバックの引数 n は number と推論されます。
それなのに toUpperCase(string のメソッド)を呼ぼうとしているので、
「n は number 型だから、string 専用メソッドは使えないよ」
というエラーになります。
コールバックでよくあるもう1つのエラーは、「引数の数/型が合わない」パターンです。
function forEachNumber(
numbers: number[],
fn: (value: number, index: number) => void
) {
numbers.forEach(fn);
}
forEachNumber([1, 2, 3], (value) => {
console.log(value);
});
// OK(index を使わないのは問題ない)
TypeScript逆に、もっと引数を取ろうとすると怒られます。
forEachNumber([1, 2, 3], (value, index, array) => {
// エラー: Expected 2 arguments, but got 3. など
});
TypeScriptエラーメッセージを読むときは、
「このコールバックは、本来どういう形(何を受け取って何を返す関数)のはずなのか?」
をまず確認するのがおすすめです。
パターン7:null / undefined を無視したときのエラー
Object is possibly ‘null’ / ‘undefined’.
関数で扱う値が null や undefined を含むとき、必ず出会います。
function printLength(text: string | null) {
console.log(text.length);
// ~~~~~
// Object is possibly 'null'.
}
TypeScript意味としては、
「text は null かもしれないから、そのまま length を読みに行くのは危ない」
です。
対応としては、どこかで nullチェックをして型を絞り込むしかありません。
function printLength(text: string | null) {
if (text === null) {
console.log("値なし");
return;
}
// ここでは text は string
console.log(text.length);
}
TypeScriptこのエラーが出たら、
そもそもこの関数の中で null をどう扱うべきか?
「null のとき何をしたいか」を決めているか?
を自分に問いかけてみてください。
「null の場合を無視する」のではなく、
「null の場合の仕様をちゃんと決める」ことが、型エラー解決と同時に設計の質も上げてくれます。
最後に:型エラーを「読める」と一気に楽になる
関数まわりで出る型エラーは、最初は全部同じに見えます。
でも、少しずつ慣れてくると、
これは「引数の数」
これは「引数の型」
これは「戻り値の型」
これは「null の可能性」
これは「オブジェクトの形」
と、エラーの種類ごとに頭の中で仕分けできるようになってきます。
その感覚を育てるために、エラーと出会ったときは必ず、
関数の「約束」(引数と戻り値の型)
実際に「そう使おうとしている自分」
を見比べてみてください。
型エラーは、あなたのことを責めているわけじゃない。
「あなたが本当に実現したい関数の姿」と、「今書いているコード」のズレを教えてくれているだけです。
そのズレを一つずつ埋めていくことが、
関数型と TypeScript を“自分の武器”に変えていくプロセスそのものです。

