アロー関数がただの代替構文ではないということは皆さまご存じかと思いますが、ではどちらをどのタイミングで使うべきなのかという論点については人によって結論がまちまちというか、まあ好みでいいじゃんみたいにはぐらかされることが多いように感じます。
ですがチーム開発ではそんなことも言っていられない1ので、今回は両者の性質の違いからstrictモード + TypeScriptの環境においてどちらをいつ使うのが合理的であるのかを考えていきます。
結論
結論から述べますと、アロー関数は
function
式を置き換えるためだけに使い
function
文を置き換えるために使うのは控えるべき
です。つまり以下のような関数を考えた場合
function double(...nums: number[]): number[] {
return nums.map(function (num) {
return num * 2
})
}
このように書くことが望ましいです。
function double(...nums: number[]): number[] {
return nums.map((num) => num * 2)
}
const double = (...nums: number[]): number[] => {
return nums.map((num) => num * 2)
}
下もネット記事などではよく見る形ですが、私も熟考の末この結論に至りました。
何故function
文はアロー関数で置き換えるべきでないのか、ここからその根拠を示していきます。
単純機能比較
その前に両者の非strictモード + JavaScriptにおける機能の比較が以下の記事に詳しいので、まずはこちらの結論項にある表をご確認ください。
JavaScript: 通常の関数とアロー関数の違いは「書き方だけ」ではない。異なる性質が10個ほどある。
このうち
arguments
- 引数名の重複
- 再定義
についてはstrictモード + TypeScriptによって禁止されるため、この環境において両者の違いは
-
this
の指すもの -
new
の使用可否 -
prototype
プロパティの有無 -
yield
の使用可否(ジェネレータ) -
super
の使用可否 - (巻き上げの有無)
の6点になります。
理由①: 安定性に関する優位性が消えた
上記の通りアロー関数で関数を定義することによって引数名の重複や同じ名前での再定義といったポカを弾くことができましたが、設定によってfunction
文を用いてもキチンとエラーが出るようになりました。
理由②: 使用できない場面が多い
良いコーディング規約とは条件分岐が少ないことだと私は考えています。
つまり「この時はこっちを使っていいけど、こういう時はあっちを使おうね」みたいなやつは極力減らした方がいいということです。
仮に『関数の宣言はアロー関数を用いること』という規約を設けたとすると、
『ただし
- コンストラクタを書く場合
- ジェネレータ関数を書く場合
- サブクラスでオーバーライドする際に
super
を呼ぶ必要がある場合(参考: TypeScript Deep Dive 日本語版)
にはアロー関数を使わないように』という面倒な補記を入れる必要があります。
これは書く方も読む方も面倒です。
理由③: this
対策として致命的な欠陥がある
それでも関数の定義にアロー関数を使いたい理由はやはりthis
かと思います。
this
の挙動を表す例としてMicrosoft謹製のwikiからコードを拝借しました。
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つしかないはずです。
それがこれ↓
const h = new Hoge(5)
setTimeout(h.print, 1000)
メソッドを渡して他の誰かに実行してもらうパターン。
上記のサンプルは1秒後にx is undefined
(strictモードでない場合はグローバルオブジェクト)が表示されます。
大抵の方はこれを防ぐためにprint
をアロー関数で定義してthis
をバインドしているかと思いますが、それって言い換えれば「メソッドがアロー関数で定義されていればメソッドの参照を渡してもいいよ」ということでつまり関数のユーザーが関数の実装を知らなければならないということになると思うんですよね。設計として致命的にヤバい。
以下の書き方であればprint
が普通に定義されているかアロー関数で定義されているかに依らずthis
をバインドすることができます。
setTimeout(h.print.bind(h), 1000)
setTimeout(() => h.print(), 1000)
直感的なコードは前者ですが、bind
の第一引数は型安全でないというそれはそれで激ヤバな特性があるので気にされる方は後者を使うことをおすすめします。
総論
上記のような思考を踏まえて
- 関数やメソッドの定義には
function
および通常の記法を用いる - 無名関数は
function
を用いずアロー関数式で書く
としました。理由としてはやはり③が一番大きいですね。そこそこ断定的な書き方をしましたがgoogleのコーディング規約にも似たようなことが書いてあるのでそんなに間違ってはないんじゃないかなと思います。
12/21 追記
いただいたコメントが大変ためになるのでと私の[返信]
(https://qiita.com/nicco_mirai/items/0bd0d0bcee72497d4b42#comment-43528ad7c51e09646e4d)と併せてぜひご確認ください。
-
個人開発の場合は好みでいいと思います。 ↩