はじめに
フックAPIリファレンスでuseCallback
の存在は知っていましたが、実際に使ったことがなかったので、使い所を調べて使ってみた話をします。
useCallbackとは
メモ化してくれる関数らしいです。
なんだかパフォーマンスを上げてくれそうな気がしますね。
Todoアプリを作りながら、試してみましょう。
Todoアプリのサンプルを作成
とりあえず、簡単なTodoアプリを作成しました。
ファイル構成はこんな感じです。
-
src/Todos/index.tsx
- Todo追加フォームと、TodoListコンポーネントをレンダリングしている
- TodoListコンポーネントには、
todos
データを渡している。
-
src/TodoList/index.tsx
- 受け取った
todos
データをループで表示 - 1件1件の
todo
データは、Todoコンポーネントに渡してレンダリングしている
- 受け取った
-
src/Todo/index.tsx
- 受け取った
todo
データを表示 - 削除ボタンを表示
- 受け取った
画面の見た目はこんな感じです。
パフォーマンスの問題点を探す
無駄にレンダリングされている場所を探します。
React Developer Tools
拡張機能のHighlight updates when components render.
で見ても良いですが、TodoList
の中でconsole.log
が何回出ているかで確認してみました。
すると、Todo追加用の入力フォームに、文字を入力するたびにTodoListコンポーネントがレンダリングされていることが分かりました。
<TodoList todos={todos} />
こんな感じで、TodoListコンポーネントをレンダリングしていますが、todos
しか渡していません。
入力値が更新されても、todos
の値は変わらないので、無駄にレンダリングされていることが分かります。
React.memoを使う
一旦本題とは別の話ですが、React.memo
は、渡されたprops
の変更のみをチェックし、変更があれば、再レンダリングされます。
-import React from "react";
+import React, { memo } from "react";
-export default TodoList;
+export default memo(TodoList);
無駄なレンダリングは解消されました。
削除ボタンが機能していないので、使えるようにする
React.memo
の話をしておく都合上削除ボタンに削除用のメソッドを渡していませんでした。
TodoListコンポーネントのレンダリングでは、todos
データ以外に、削除用関数を渡すような書き方に変えます。
<TodoList todos={todos} delTodo={delTodo} />
TodoListが無駄にレンダリングされるようになってしまった。。。
delTodo
関数を渡すようにしただけで、またTodoListコンポーネントが無駄にレンダリングされるようになってしまいました。
どうやら、Todosコンポーネントが再レンダリングされるたび(入力フォームに変更があるたび)に、delTodo
の関数が作り直され、別物扱いとなり、TodoListコンポーネント側では、新しく作り直されたdelTodo
メソッドを受け取るので、変更されたと判断され、再度、レンダリングしているようです。
useCallbackを使う
やっと本題です。
delTodo
メソッドをメモ化して、新しく作られないようにします。
-<TodoList todos={todos} delTodo={delTodo} />
+<TodoList todos={todos} delTodo={useCallback(delTodo, [])} />
TodoListコンポーネントに渡しているdelTodo
メソッドがメモ化され、同じものが使われるようになり、無駄なレンダリングが発生しないようになりました。
delTodo
メソッドを常に同じものを使うようにメモ化しましたが、状況によっては、作り直して欲しい場合もあるかと思います。
その際は、useCallback
の第2引数の配列を使います。
例えば、hoge
という変数が変わった時に、delTodo
メソッドも作り直して欲しい場合、useCallback(delTodo, [hoge])
となります。
おわりに
ほとんど参考にしたサイトのままとなってしまいましたが、自分の体験した使い所をご紹介しました。