TypeScript | 関数・クラス・ジェネリクス:関数設計の深化 – アロー関数の型推論

TypeScript
スポンサーリンク

まず「アロー関数の型推論」で何が起きているか

アロー関数はこういう形の関数です。

const add = (a, b) => a + b;
TypeScript

TypeScript では、ここに 全部の型を書かなくても
コンパイラが「文脈」から型を推論してくれます。

アロー関数の型推論で大事なのは、この2つです。

  • 引数の型をどうやって決めているか
  • 戻り値の型をどうやって決めているか

そしてもう1つ、
「どこまで推論に任せてよくて、どこからは明示した方がいいか」
という感覚です。

ここから、例を通してじっくり固めていきます。


基本1:戻り値の型は「書いた式」から自動で決まる

単純なアロー関数の推論

const add = (a: number, b: number) => {
  return a + b;
};
TypeScript

このとき、add の型は自動的にこう推論されます。

// 推論される型
const add: (a: number, b: number) => number;
TypeScript

戻り値の型 number は、
return a + b の式から決まっています。

1行で書いても同じです。

const add = (a: number, b: number) => a + b;
//           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// ここから (a: number, b: number) => number と推論される
TypeScript

ここでのポイントは、

アロー関数は「本体の式」から戻り値の型をほぼ自動で決めてくれるので、
戻り値の型をわざわざ書かなくていい場面が多い

ということです。

戻り値の型を明示したいときだけ、こう書きます。

const add = (a: number, b: number): number => a + b;
TypeScript

でも、基本的には省略しても問題ありません。


基本2:引数の型は「文脈」から推論される

配列メソッドとの組み合わせでよく起きる推論

一番分かりやすいのが map などの配列メソッドです。

const numbers = [1, 2, 3];

const doubled = numbers.map((n) => n * 2);
TypeScript

ここで、(n) => n * 2n に型を書いていません。
それでも TypeScript は nnumber と推論します。

なぜかというと、

  • numbersnumber[]
  • Array.prototype.map の型は
    (callback: (value: number, index: number, array: number[]) => U) => U[]
  • だから、callback の第1引数 valuenumber

という情報があるからです。

つまり、

「アロー関数が“どこに渡されているか”を見て、その引数の型を決めている」

これが「文脈からの型推論」です。

自分で関数の引数に「関数型」を書いた場合も同じ

type Mapper = (value: string) => number;

function useMapper(fn: Mapper) {
  const result = fn("hello");
  console.log(result);
}

useMapper((v) => v.length);
TypeScript

ここでも (v) => v.lengthv に型を書いていませんが、
Mapper(value: string) => number なので、vstring と推論されます。

「アロー関数の引数の型は、“受け取る側の関数の型”から逆算される」
というイメージを持っておくと、かなりスッキリします。


基本3:型推論が効かないときは「noImplicitAny」で気づける

文脈がないと引数は any になりがち

例えば、こう書いたとします。

const fn = (x) => {
  return x * 2;
};
TypeScript

この x は、文脈がなければ any と推論されます。
noImplicitAny が有効ならエラーになります)

fn の型もこうなります。

const fn: (x: any) => any;
TypeScript

これは TypeScript 的にはあまり嬉しくない状態です。
「型安全にしたいのに、結局 any になっている」からです。

こういうときは「引数の型だけは明示する」が基本

文脈がないアロー関数では、
「引数の型だけはちゃんと書く」 をルールにしておくと安全です。

const fn = (x: number) => {
  return x * 2;
};
TypeScript

戻り値の型は number と推論されるので、
わざわざ書かなくても OK です。

「引数の型は自分で決める」「戻り値は式から推論させる」
この分担が、アロー関数と型推論の一番気持ちいい使い方です。


応用1:アロー関数とジェネリクスの型推論

map 的な「汎用関数」を自分で書く場合

例えば、配列を変換する関数を自作するとします。

function mapArray<T, U>(array: T[], fn: (value: T) => U): U[] {
  const result: U[] = [];
  for (const item of array) {
    result.push(fn(item));
  }
  return result;
}
TypeScript

これを使うとき、アロー関数の型推論が効きます。

const lengths = mapArray(["a", "bb", "ccc"], (s) => s.length);
TypeScript

ここで起きている推論はこうです。

  • arraystring[] を渡したので、Tstring
  • fn の型は (value: T) => U なので、valuestring
  • (s) => s.lengthsstring と推論される
  • s.lengthnumber なので、Unumber
  • 結果として mapArray の戻り値は number[]

つまり、ジェネリック関数の型パラメータ(T, U)も、アロー関数の中身から逆算されて決まる わけです。

ここまで来ると、

「アロー関数の型推論」
=「呼び出し側の引数・返り値・アロー関数の中身・ジェネリクスが全部つながって、TypeScript が一気に解いてくれている」

というイメージが持てるはずです。


応用2:オブジェクトのメソッドにアロー関数を使うときの推論

オブジェクトリテラル+アロー関数

const user = {
  name: "Taro",
  greet: () => {
    console.log("こんにちは");
  },
};
TypeScript

ここでは greet の型は () => void と推論されます。

ただし、アロー関数は this を持たないので、
this を使いたいメソッドには向きません。

const user = {
  name: "Taro",
  greet() {
    console.log("こんにちは、" + this.name); // こっちはOK
  },
};
TypeScript

アロー関数は「this を外側から捕まえる」性質があるので、
クラスのプロパティやコールバックには向いていますが、
オブジェクトリテラルのメソッドで this を使いたいときは、
通常のメソッド記法の方が TypeScript 的にも素直です。

ここでのポイントは、

アロー関数の型推論は便利だけど、「this をどう扱うか」まで含めて設計する必要がある

ということです。


どこまで推論に任せて、どこから明示するか

推論に任せていいところ

戻り値の型(単純な式ならほぼ任せてOK)
配列メソッド(map, filter など)のコールバック引数
ジェネリック関数に渡すアロー関数の引数・戻り値

これらは、文脈がしっかりしているので、
TypeScript がかなり賢く推論してくれます。

明示した方がいいところ

文脈のないアロー関数の引数((x) => ... だけ書いている場合)
外部に公開する「ライブラリ的な関数」の型(API の顔になる部分)
複雑なジェネリクスで、推論結果を自分でコントロールしたいとき

特に初心者のうちは、

「引数の型は自分で書く」「戻り値は推論に任せる」

を基本ルールにしておくと、
「どこが any になっているか分からない」という事故をかなり防げます。


まとめ:「アロー関数の型推論」を自分の言葉で言うと

最後に、あなた自身の言葉でこう整理してみてください。

アロー関数は、
「戻り値の型は中で書いた式から勝手に決めてくれる」
「引数の型は“どこに渡しているか”という文脈から逆算してくれる」

だから、

配列の map や、自分で定義した (value: T) => U みたいな関数型と組み合わせると、
ほとんど型を書かなくても、ちゃんと型安全なコードになる。

ただし、文脈がないアロー関数は、
引数が any になりやすいので、
「引数の型だけはちゃんと書く」を自分ルールにしておくと安心。

コードを書きながら、
「このアロー関数の引数の型、TypeScript はどこから推論しているんだろう?」
と一度立ち止まって考えてみてください。

その問いを繰り返すうちに、
型推論を「なんとなくの魔法」ではなく、
自分でコントロールできる道具として扱えるようになっていきます。

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