TypeScript | 基礎文法:関数の基礎 – 関数でよく出る型エラー

TypeScript
スポンサーリンク

はじめに:型エラーは「怒られている」のではなく「守られている」

関数まわりで出る型エラーは、最初かなりストレスになりますよね。
でも本質的には、「その書き方だと、実行時にバグる可能性が高いよ」と 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.
TypeScript

TypeScript は、「関数が宣言している引数の数」と「実際に渡している数」が違うと止めます

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 | nullstring | 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

ここでは numbersnumber[] なので、
map のコールバックの引数 nnumber と推論されます。

それなのに 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’.

関数で扱う値が nullundefined を含むとき、必ず出会います。

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 を“自分の武器”に変えていくプロセスそのものです。

タイトルとURLをコピーしました