リファレンス

useCallback(fn, dependencies)

コンポーネントのトップレベルで useCallback を呼び出し、再レンダー間で関数定義をキャッシュします。

引数

  • fn: キャッシュしたい関数型の値。任意の引数を取り、任意の値を返すことができます。React は初回のレンダー時にはその関数をそのまま返します(呼び出しません!)。次回以降のレンダーでは、ひとつ前のレンダー時から dependencies が変更されていない場合、React は再び同じ関数を返します。それ以外の場合は、今回のレンダー時に渡された関数を返しつつ、後で再利用できる場合に備えて保存します。React は関数を呼び出しません。関数自体が返されるので、呼ぶか呼ばないか、いつ呼ぶのかについてはあなたが決定できます。

  • dependencies: fn コード内で参照されるすべてのリアクティブな値のリストです。リアクティブな値には、props、state、コンポーネント本体に直接宣言されたすべての変数および関数が含まれます。リンタが 、すべてのリアクティブな値が依存値として正しく指定されているか確認できます。依存値のリストは要素数が一定である必要があり、[dep1, dep2, dep3] のようにインラインで記述する必要があります。React は、 を使った比較で、それぞれの依存値を以前の値と比較します。

返り値

初回のレンダー時、useCallback は渡された fn 関数を返します。

その後のレンダー時には、前回のレンダーからすでに保存されている fn 関数を返すか(依存配列が変更されていない場合)、このレンダー時に渡された fn 関数を返します。

注意点

  • useCallback はフックですので、コンポーネントのトップレベルまたは独自のフックでのみ呼び出すことができます。ループや条件の中で呼び出すことはできません。それが必要な場合は、新しいコンポーネントを抽出し、その中にその状態を移動させてください。
  • React は、特定の理由がない限り、キャッシュされた関数を破棄しません。たとえば、開発環境では、コンポーネントのファイルを編集すると React はキャッシュを破棄します。開発環境と本番環境の両方で、初回マウント時にコンポーネントがサスペンドすると、React はキャッシュを破棄します。将来的に、React はキャッシュを破棄することを活用したさらなる機能を追加するかもしれません。例えば、将来的に React が仮想化リストに対する組み込みサポートを追加する場合、仮想化されたテーブルのビューポートからスクロールアウトした項目のキャッシュを破棄することが理にかなっています。これは、useCallback をパフォーマンスの最適化として利用する場合に期待に沿った動作となります。そうでない場合は、 や の方が適切かもしれません。

使用法

コンポーネントの再レンダーをスキップする

レンダーのパフォーマンスを最適化する際には、子コンポーネントに渡す関数をキャッシュする必要があることがあります。まずは、これを実現するための構文を見て、その後、どのような場合に便利かを見ていきましょう。

コンポーネントの再レンダー間で関数をキャッシュするには、その定義を useCallback フックでラップします。

useCallback には 2 つの要素を渡す必要があります。

  1. 再レンダー間でキャッシュしたい関数定義。
  2. 関数内で使用される、コンポーネント内のすべての値を含む依存値のリスト

初回のレンダー時に useCallback から返される関数、つまりあなたが受け取る関数は、あなたが渡した関数そのものになります。

次回以降のレンダーでは、React は依存配列を前回のレンダー時に渡した依存配列と比較します。( を使った比較で)依存配列が変更されていない場合、useCallback は前回と同じ関数を返します。それ以外の場合、useCallback今回のレンダーで渡された関数を返します。

言い換えると、useCallback は依存配列が変更されるまでの再レンダー間で関数をキャッシュします。

例を通して、これが有用な場合を見ていきましょう

例えば、ProductPage から ShippingForm コンポーネントに handleSubmit 関数を渡しているとします。

theme プロパティを切り替えるとアプリが一瞬フリーズすることに気付きましたが、JSX から <ShippingForm /> を取り除くと、高速に感じられるのだとしましょう。これは ShippingForm コンポーネントの最適化を試みる価値があることを示してます。

デフォルトでは、コンポーネントが再レンダーされると、React はその子要素すべてを再帰的に再レンダーします。これが、ProductPage が異なる theme で再レンダーされると、ShippingForm コンポーネント再レンダーされる理由です。再レンダーに多くの計算を必要としないコンポーネントにとっては問題ありません。しかし、再レンダーが遅いことを確認できたなら、 でラップすることで、props が前回のレンダー時と同じである場合に ShippingForm に再レンダーをスキップするように指示することができます。

この変更により、すべての props が前回のレンダー時と同じ場合、ShippingForm は再レンダーをスキップするようになります。ここで関数のキャッシュが重要になってきます! handleSubmituseCallback なしで定義したとしましょう。

JavaScript では、function () {} または () => {} は常に異なる関数を作成します。これは {} のオブジェクトリテラルが常に新しいオブジェクトを作成するのと似ています。通常、これは問題になりませんが、ShippingForm の props が決して同じにならないので による最適化は機能しなくなるということでもあります。このようなときに有用になってくるのが useCallback です。

handleSubmituseCallback でラップすることで、再レンダー間でそれを同一の関数にすることができます(依存配列が変更されるまで)。それをする特定の理由がない限り、関数を useCallback でラップする必要はありません。今回の例における理由とは、この関数を でラップされたコンポーネントに渡せば再レンダーをスキップできるということです。このページの後半で説明されているように、useCallback が必要な他の理由もあります。

さらに深く知る

useCallback と並んで をよく見かけることでしょう。子コンポーネントを最適化しようとするとき、どちらも有用です。これらはあなたが下位に渡している何かを(言い換えると、キャッシュする)ことを可能にします。

その違いはキャッシュできる内容です。

  • はあなたの関数の呼び出し結果をキャッシュします。この例では、product が変更されない限り、computeRequirements(product) の呼び出し結果をキャッシュします。これにより、ShippingForm を不必要に再レンダーすることなく、requirements オブジェクトを下位に渡すことができます。必要に応じて、React はレンダー中にあなたが渡した関数を呼び出して結果を計算します。
  • useCallback関数自体をキャッシュしますuseMemo とは異なり、あなたが提供する関数を呼び出しません。代わりに、あなたが提供した関数をキャッシュして、productId または referrer が変更されない限り、handleSubmit 自体が変更されないようにします。これにより、ShippingForm を不必要に再レンダーすることなく、handleSubmit 関数を下位に渡すことができます。ユーザがフォームを送信するまであなたのコードは実行されません。

すでに に詳しい場合、useCallback を次のように考えると役立つかもしれません。

さらに深く知る

あらゆる場所に useCallback を追加すべきか?

あなたのアプリがこのサイトのように、ほとんどのインタラクションが大まかなもの(ページ全体やセクション全体の置き換えなど)である場合、メモ化は通常不要です。一方、あなたのアプリが描画エディタのようなもので、ほとんどのインタラクションが細かなもの(図形を移動させるなど)である場合、メモ化は非常に役に立つでしょう。

useCallback で関数をキャッシュすることが有用なのはいくつかのケースに限られます。

  • それを でラップされたコンポーネントに props として渡すケース。この場合は、値が変化していない場合には再レンダーをスキップしたいでしょう。メモ化することで、依存値が異なる場合にのみコンポーネントを再レンダーさせることができます。
  • あなたが渡している関数が、後で何らかのフックの依存値として使用されるケース。たとえば、他の useCallback でラップされた関数がそれに依存している、または からこの関数に依存しているケースです。

これらのケース以外では、関数を useCallback でラップすることにメリットはありません。それを行っても重大な害はないため、個別のケースを考えずに、可能な限りすべてをメモ化するようにしているチームもあります。このアプローチのデメリットは、コードが読みにくくなることです。また、すべてのメモ化が効果的なわけではありません。例えば、毎回変化する値が 1 つ存在するだけで、コンポーネント全体のメモ化が無意味になってしまうこともあります。

useCallback は関数の作成を防ぐわけではないことに注意してください。あなたは常に関数を作成しています(それは問題ありません!)。しかし、何も変わらない場合、React はそれを無視し、キャッシュされた関数を返します。

実際には、以下のいくつかの原則に従うことで、多くのメモ化を不要にすることができます

  1. コンポーネントが他のコンポーネントを視覚的にラップするときは、それが。これにより、ラッパコンポーネントが自身の state を更新しても、React はその子を再レンダーする必要がないことを認識します。
  2. ローカル state を優先し、必要以上に を行わないようにします。フォームや、アイテムがホバーされているかどうか、といった頻繁に変化する state は、ツリーのトップやグローバルの状態ライブラリに保持しないでください。
  3. 保ちます。コンポーネントの再レンダーが問題を引き起こしたり、何らかの目に見える視覚的な結果を生じたりする場合、それはあなたのコンポーネントのバグです! メモ化を追加するのではなく、バグを修正します。
  4. 。React アプリケーションのパフォーマンス問題の大部分は、エフェクト内での連鎖的な state 更新によってコンポーネントのレンダーが何度も引き起こされるために生じます。
  5. 。例えば、メモ化する代わりに、オブジェクトや関数をエフェクトの中や外に移動させるだけで、簡単に解決できる場合があります。

それでも特定のインタラクションが遅いと感じる場合は、、どのコンポーネントでのメモ化が最も有効かを確認し、そこでメモ化を行いましょう。これらの原則を守ることで、コンポーネントのデバッグや理解が容易になるため、常に原則に従うことをおすすめします。長期的には、この問題を一挙に解決できるについて研究を行っています。


メモ化されたコールバックからの state 更新

場合によっては、メモ化されたコールバックから前回の state に基づいて state を更新する必要があります。

この handleAddTodo 関数は、次の todo リストを計算するために todos を依存値として指定します。

通常、メモ化された関数からは可能な限り依存値を少なくしたいと思うでしょう。何らかの state を次の state を計算するためだけに読み込んでいる場合、代わりに を渡すことでその依存値を削除できます。

ここでは、todos を依存値として内部で読み込む代わりに、どのように state を更新するかについての指示(todos => [...todos, newTodo])を React に渡します。。


エフェクトが頻繁に発火するのを防ぐ

時々、 の内部から関数を呼び出したいことがあるかもしれません。

これには問題があります。。しかし、createOptions を依存値として宣言すると、あなたのエフェクトがチャットルームに常に再接続することになります。

これを解決するために、エフェクトから呼び出す必要がある関数を useCallback でラップすることができます。

これにより、roomId が同じ場合に再レンダー間で createOptions 関数が同じであることが保証されます。しかし、関数型の依存値を必要としないようにする方がさらに望ましいでしょう。関数をエフェクトの内部に移動します。

これでコードはよりシンプルになり、useCallback が不要になりました。。


カスタムフックの最適化

あなたがを書いている場合、それが返すあらゆる関数は useCallback でラップすることが推奨されます。

これにより、フックを利用する側が必要に応じて自身のコードを最適化することができます。


トラブルシューティング

コンポーネントがレンダーするたびに useCallback が異なる関数を返す

第 2 引数として依存配列を指定したかを確認してください!

依存配列を忘れると、useCallback は毎回新しい関数を返します。

以下は、第 2 引数として依存配列を渡す修正版です。

これでもうまくいかない場合、問題は、少なくとも 1 つの依存値が前回のレンダーと異なることです。依存値を手動でコンソールにログ出力することで、この問題をデバッグできます。

その後、コンソール内の異なる再レンダーからの配列を右クリックすると、それぞれに対して「グローバル変数として保存」が選択できます。最初のものが temp1 として、2 つ目が temp2 として保存されたと仮定すると、ブラウザのコンソールを使用して、両方の配列内の各依存値が同一であるかどうかを以下のように確認できます。

メモ化を壊している依存値を見つけたら、それを取り除く方法を見つけるか、または


ループ内の各リスト要素で useCallback を呼び出す必要があるが、それは許されていない

Chart コンポーネントが でラップされていると仮定します。ReportList コンポーネントが再レンダーするときに、リスト内の Chart がすべて再レンダーされてしまわないよう、一部をスキップしたいとしましょう。しかし、ループの中で useCallback を呼び出すことはできません。

代わりに、個々のアイテムに対応するコンポーネントを抽出し、その中に useCallback を配置します。

もしくは、最後のスニペットから useCallback を削除し、代わりに Report 自体を でラップすることもできます。item プロパティが変更されない場合、Report は再レンダーをスキップするため、Chart も再レンダーをスキップします。