【TypeScript】結局のところfunctionとアロー関数はどちらを使うべきなのか？

アロー関数がただの代替構文**ではない**ということは皆さまご存じかと思いますが、ではどちらをどのタイミングで使うべきなのかという論点については人によって結論がまちまちというか、**まあ好みでいいじゃん**みたいにはぐらかされることが多いように感じます。

ですがチーム開発ではそんなことも言っていられない[^team]ので、今回は両者の性質の違いから**strictモード + TypeScriptの環境において**どちらをいつ使うのが合理的であるのかを考えていきます。


# 結論

結論から述べますと、アロー関数は

**`function`式**を置き換えるためだけに使い
**`function`文**を置き換えるために使うのは控えるべき

です。つまり以下のような関数を考えた場合

```typescript
function double(...nums: number[]): number[] {
  return nums.map(function (num) {
    return num * 2
  })
}
```

このように書くことが望ましいです。

```typescript:good
function double(...nums: number[]): number[] {
  return nums.map((num) => num * 2)
}
```

```typescript:bad
const double = (...nums: number[]): number[] => {
  return nums.map((num) => num * 2)
}
```

下もネット記事などではよく見る形ですが、私も熟考の末この結論に至りました。
何故`function`文はアロー関数で置き換えるべきでないのか、ここからその根拠を示していきます。

# 単純機能比較

その前に両者の**非strictモード + JavaScript**における機能の比較が以下の記事に詳しいので、まずはこちらの結論項にある表をご確認ください。

[**JavaScript: 通常の関数とアロー関数の違いは「書き方だけ」ではない。異なる性質が10個ほどある。**](https://qiita.com/suin/items/a44825d253d023e31e4d)

このうち

- `arguments`
- 引数名の重複
- 再定義

については**strictモード + TypeScript**によって禁止されるため、この環境において両者の違いは

- `this`の指すもの
- `new`の使用可否
- `prototype`プロパティの有無
- `yield`の使用可否(ジェネレータ)
- `super`の使用可否
- (巻き上げの有無)

の6点になります。


# 理由①: 安定性に関する優位性が消えた
上記の通りアロー関数で関数を定義することによって引数名の重複や同じ名前での再定義といったポカを弾くことができましたが、設定によって`function`文を用いてもキチンとエラーが出るようになりました。


# 理由②: 使用できない場面が多い
良いコーディング規約とは**条件分岐が少ないこと**だと私は考えています。
つまり「この時はこっちを使っていいけど、こういう時はあっちを使おうね」みたいなやつは極力減らした方がいいということです。

仮に『関数の宣言はアロー関数を用いること』という規約を設けたとすると、
『ただし

- コンストラクタを書く場合
- ジェネレータ関数を書く場合
- サブクラスでオーバーライドする際に`super`を呼ぶ必要がある場合(参考: [**TypeScript Deep Dive 日本語版**](https://typescript-jp.gitbook.io/deep-dive/future-javascript/arrow-functions#tiparto))

にはアロー関数を使わないように』という面倒な補記を入れる必要があります。
これは書く方も読む方も面倒です。


# 理由③: `this`対策として致命的な欠陥がある
それでも関数の定義にアロー関数を使いたい理由はやはり`this`かと思います。
`this`の挙動を表す例として[**Microsoft謹製のwiki**](https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript#what-is-this-in-javascript)からコードを拝借しました。

```typescript
class Hoge {
  constructor(public x: number) {}

  print(): void {
    console.log("x is " + this?.x)
  }
}

const hoge = new Hoge(3)
hoge.print() // x is 3

const fuga = { x: 10, print: hoge.print }
fuga.print() // x is 10

const piyo = fuga.print
piyo() // x is undefined
```

まあ確かにキモいですけど。でも正直こんなコードは**書かない**ですよね？
普通にTypeScriptを書いていて遭遇する`this`の壊れパターンなんて実際には1つしかないはずです。
それがこれ↓

```typescript
const h = new Hoge(5)
setTimeout(h.print, 1000)
```

**メソッドを渡して他の誰かに実行してもらうパターン**。
上記のサンプルは1秒後に`x is undefined`(strictモードでない場合はグローバルオブジェクト)が表示されます。

大抵の方はこれを防ぐために`print`をアロー関数で定義して`this`をバインドしているかと思いますが、それって言い換えれば「メソッドがアロー関数で定義されていればメソッドの参照を渡してもいいよ」ということでつまり**関数のユーザーが関数の実装を知らなければならない**ということになると思うんですよね。設計として致命的にヤバい。

以下の書き方であれば`print`が普通に定義されているかアロー関数で定義されているかに**依らず**`this`をバインドすることができます。

```typescript
setTimeout(h.print.bind(h), 1000)
setTimeout(() => h.print(), 1000)
```

直感的なコードは前者ですが、`bind`の第一引数は**型安全でない**というそれはそれで激ヤバな特性があるので気にされる方は後者を使うことをおすすめします。


# 総論

上記のような思考を踏まえて

- 関数やメソッドの定義には`function`および通常の記法を用いる
- 無名関数は`function`を用いずアロー関数式で書く

としました。理由としてはやはり③が一番大きいですね。そこそこ断定的な書き方をしましたが[**googleのコーディング規約**](https://google.github.io/styleguide/tsguide.html#arrow-functions-as-properties)にも似たようなことが書いてあるのでそんなに間違ってはないんじゃないかなと思います。


# 12/21 追記
[**いただいたコメント**](https://qiita.com/nicco_mirai/items/0bd0d0bcee72497d4b42#comment-7a7a0e8750a611f6e68e)が大変ためになるのでと私の[**返信**]
(https://qiita.com/nicco_mirai/items/0bd0d0bcee72497d4b42#comment-43528ad7c51e09646e4d)と併せてぜひご確認ください。


[^team]: 個人開発の場合は**好みでいい**と思います。