【React hooks】 useCallback って何？

# はじめに

[フックAPIリファレンス](https://ja.reactjs.org/docs/hooks-reference.html#usecallback)で`useCallback`の存在は知っていましたが、実際に使ったことがなかったので、使い所を調べて使ってみた話をします。

# useCallbackとは

https://ja.reactjs.org/docs/hooks-reference.html#usecallback

メモ化してくれる関数らしいです。

なんだかパフォーマンスを上げてくれそうな気がしますね。

Todoアプリを作りながら、試してみましょう。

## Todoアプリのサンプルを作成

https://github.com/pure-adachi/sample-react-hooks-use-callback/tree/sample-todo-app

とりあえず、簡単なTodoアプリを作成しました。

ファイル構成はこんな感じです。

- `src/Todos/index.tsx`
    - Todo追加フォームと、TodoListコンポーネントをレンダリングしている
    - TodoListコンポーネントには、`todos`データを渡している。
- `src/TodoList/index.tsx`
    - 受け取った`todos`データをループで表示
    - 1件1件の`todo`データは、Todoコンポーネントに渡してレンダリングしている
- `src/Todo/index.tsx`
    - 受け取った`todo`データを表示
    - 削除ボタンを表示

画面の見た目はこんな感じです。

![スクリーンショット 2022-03-21 3.27.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/68cc6876-00d9-b660-e7f4-38590a4a31b6.png)

## パフォーマンスの問題点を探す

無駄にレンダリングされている場所を探します。

`React Developer Tools`拡張機能の`Highlight updates when components render.`で見ても良いですが、`TodoList`の中で`console.log`が何回出ているかで確認してみました。

すると、Todo追加用の入力フォームに、文字を入力するたびにTodoListコンポーネントがレンダリングされていることが分かりました。

![input todo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/644ba67f-ce81-b14d-104d-bfaa76bd5138.gif)

```tsx
<TodoList todos={todos} />
```
こんな感じで、TodoListコンポーネントをレンダリングしていますが、`todos`しか渡していません。
入力値が更新されても、`todos`の値は変わらないので、無駄にレンダリングされていることが分かります。

## React.memoを使う

https://ja.reactjs.org/docs/react-api.html#reactmemo

一旦本題とは別の話ですが、`React.memo`は、渡された`props`の変更のみをチェックし、変更があれば、再レンダリングされます。

```diff:src/TodoList/index.tsx
-import React from "react";
+import React, { memo } from "react";
```

```diff:src/TodoList/index.tsx
-export default TodoList;
+export default memo(TodoList);
```

https://github.com/pure-adachi/sample-react-hooks-use-callback/compare/sample-todo-app...add-React-memo

無駄なレンダリングは解消されました。

![input todo2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/257e7a7c-9153-6cf4-83ef-2b18cc5e90ef.gif)

## 削除ボタンが機能していないので、使えるようにする

~~`React.memo`の話をしておく都合上削除ボタンに削除用のメソッドを渡していませんでした。~~

TodoListコンポーネントのレンダリングでは、`todos`データ以外に、削除用関数を渡すような書き方に変えます。

```tsx
<TodoList todos={todos} delTodo={delTodo} />
```

https://github.com/pure-adachi/sample-react-hooks-use-callback/compare/add-React-memo...delete-todo

## TodoListが無駄にレンダリングされるようになってしまった。。。

`delTodo`関数を渡すようにしただけで、またTodoListコンポーネントが無駄にレンダリングされるようになってしまいました。

![add todo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/f0ed71d0-fee0-1a1b-0b88-c4185b744341.gif)

どうやら、Todosコンポーネントが再レンダリングされるたび（入力フォームに変更があるたび）に、`delTodo`の関数が作り直され、別物扱いとなり、TodoListコンポーネント側では、新しく作り直された`delTodo`メソッドを受け取るので、変更されたと判断され、再度、レンダリングしているようです。

## useCallbackを使う

やっと本題です。

`delTodo`メソッドをメモ化して、新しく作られないようにします。

```diff:src/Todos/index.tsx
-<TodoList todos={todos} delTodo={delTodo} />
+<TodoList todos={todos} delTodo={useCallback(delTodo, [])} />
```

https://github.com/pure-adachi/sample-react-hooks-use-callback/compare/delete-todo...add-use-callback

TodoListコンポーネントに渡している`delTodo`メソッドがメモ化され、同じものが使われるようになり、無駄なレンダリングが発生しないようになりました。

![useCallback.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/6bc9bc05-e615-b034-9c50-e057e2bc82bd.gif)

`delTodo`メソッドを常に同じものを使うようにメモ化しましたが、状況によっては、作り直して欲しい場合もあるかと思います。
その際は、`useCallback`の第2引数の配列を使います。
例えば、`hoge`という変数が変わった時に、`delTodo`メソッドも作り直して欲しい場合、`useCallback(delTodo, [hoge])`となります。

# おわりに

ほとんど参考にしたサイトのままとなってしまいましたが、自分の体験した使い所をご紹介しました。

# 参考文献

- [5歳娘「パパのReact、めっちゃ遅いね！」 - Qiita](https://qiita.com/Yametaro/items/621bfd8c894f8fd36539)