はじめに
React で onClick などのイベントハンドラに引数付き関数を渡すとき、なぜ () => で包む必要があるのか、最初はよくわからないままなんとなく書いていました。今回はその仕組みを JavaScript の式の評価タイミングまで掘り下げて整理します。
結論
onClick={onClickDelete(index)} と書くとレンダリング時に即実行されてしまいます。
これをonClick={() => onClickDelete(index)} と書くことで、クリック時に呼ばれる関数オブジェクト(実行方法を記したメモ) を渡す形 にすることができます。
詳細
「関数の参照」と「関数の実行結果」は別物
// onClickDelete(index) の実行結果(戻り値)を渡している
<button onClick={onClickDelete(index)}>削除</button>
// クリック時に呼ぶ関数オブジェクトを渡している
<button onClick={() => onClickDelete(index)}>削除</button>
| 書き方 |
onClick に渡るもの |
実行タイミング |
|---|---|---|
onClick={onClickDelete(index)} |
onClickDelete(index) の戻り値
|
レンダリング時に即実行 |
onClick={() => onClickDelete(index)} |
関数オブジェクト | クリックされた時に実行 |
なぜ即実行されるのか―JSX はオブジェクト生成に変換される
JSX は内部的に次のようなオブジェクト生成コードに変換されます。
{
type: 'button',
props: {
onClick: onClickDelete(index), // ← プロパティ評価時に即実行される
children: '削除'
}
}
JavaScript エンジンはオブジェクトを生成するときに各プロパティの値をその場で評価します。onClickDelete(index) は「式」なので、レンダリングのたびに即実行されてしまいます。
() => で包むと評価が遅延される
onClick: () => onClickDelete(index)
() => onClickDelete(index) はアロー関数の定義式です。これを評価すると、
- エンジンが「関数の定義」と認識する
-
中身(
onClickDelete(index))は実行せず、「後で呼ばれたら実行する処理」としてメモリに保持 - その「関数オブジェクト」を値として返す
という解釈になり、レンダリングごとに中のコードが実行されるのではなく、後で呼び出されるべき関数を作成したことになります。
引数なしの場合は () => が不要
引数が不要な場合は関数参照をそのまま渡せます。() => が必要なのは、引数を渡すために「ラッパー関数」が必要になるからです。
// 引数なし → 関数参照をそのまま渡せる
<button onClick={handleClick}>クリック</button>
// 引数あり → ラッパー関数で包む必要がある
<button onClick={() => onClickDelete(index)}>削除</button>
() => は function() {} の短縮形
() => onClickDelete(index) はアロー関数という構文の無名関数であり、function() {} と本質的に同じです。
// function() を使った書き方
<button onClick={function() { onClickDelete(index) }}>削除</button>
// アロー関数を使った書き方(一般的)
<button onClick={() => onClickDelete(index)}>削除</button>
アロー関数と function には this の扱いに違いがありますが、React の関数コンポーネントでは this を使わないため実用上は同じです。
おわりに
「なんとなく () => を書く」から「JavaScript のオブジェクト評価の仕組みとして理解する」に変わると、似たパターンに遭遇したときに迷わなくなりますね。コードを書いて進めるだけでなく、こうして一度立ち止まって仕組みを掘り下げることの大切さを改めて感じました。