3秒でわかる
あとで呼び返してもらうために引数として渡す関数。通信が終わった時点の処理や、子から親へ値を通知したい場面で広く使われます。
もう少し詳しく
どういうものか
関数を引数として別の関数に渡し、渡した先の都合のよいタイミングで呼び返してもらう仕組み。渡された側は「いつ呼ぶか」を知っていて、渡した側は「何をするか」を持っている。この分担が、非同期処理やイベント処理の基本の形になる。
JavaScript では関数が値として扱えるため、変数に入れたり引数に渡したりできる。addEventListener の第 2 引数、配列の map や filter に渡す関数、setTimeout の第 1 引数は、どれもコールバックになる。
なぜ必要か
通信やタイマーの結果は、コードを書いた順に返ってくるとは限らない。返ってきた時点で動かしたい処理を関数の形で預けておけば、待っている間もほかの処理を止めずに済む。React では、子コンポーネントから親へ値を返す手段としても使われる。親が関数を props で渡し、子はイベント発生時にそれを呼ぶ。
具体例
React で、子の入力を親が受け取る形。
function SearchBox({ onSearch }) { // 親から関数を受け取る
const [text, setText] = useState("");
return (
<form
onSubmit={(e) => {
e.preventDefault();
onSearch(text); // ここで親の関数を呼び返す
}}
>
<input value={text} onChange={(e) => setText(e.target.value)} />
</form>
);
}
function Page() {
const handleSearch = (keyword) => {
console.log("検索語は", keyword);
};
return <SearchBox onSearch={handleSearch} />;
}子は検索結果の使い道を知らないまま、通知だけを担当している。
つまずきやすいところ
渡すべき関数を、その場で呼んでしまう書き間違いが多い。onClick={handleClick()} と書くとレンダリング中に実行され、戻り値が渡ってしまう。渡すのは onClick={handleClick} の形になる。
非同期を重ねると、コールバックの中にコールバックを書く入れ子が深くなる。読みにくく、エラー処理も分散するため、現在は Promise と async / await に置き換えるのが定石になる。
React では、毎回新しい関数を作って props に渡すと子の再レンダリングが増える。memo と組み合わせる場合は useCallback で参照を固定する。
似た用語との違い
コールバックは「終わったら呼んでね」と関数を預ける形、Promise は「終わったら教えてくれる引換券」を受け取る形になる。async / await は Promise を同期的な見た目で書けるようにした構文にすぎない。