19
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

正しいuseCallback()の使い方

Last updated at Posted at 2021-08-02

今回、useCallbackを理解する上で、参考にさせていただいた記事がこちらです。
Your Guide to React.useCallback()

こちらを翻訳してまとめたものになります。掲載許可済みです。
Dmitri Pavlutinさん、ご協力ありがとうございます😢
「Good luck in your journey to mastering Frontend development!」
と、とても優しい方で、すっかりファンになってしまった。
スクリーンショット 2021-08-02 22.41.08.png

その前に、関数の等価性チェックを理解する。

function factory() {
  return (a, b) => a + b;
}

const sum1 = factory();
const sum2 = factory();

sum1(1, 2); // => 3
sum2(1, 2); // => 3

sum1 === sum2; // => false
sum1 === sum1; // => true

例えばfactory()から生成されたsum1sum2は異なる関数オブジェクトであることがわかる。

sum1 === sum2 // => false
sum1 === sum1 // => true

全てのオブジェクトは、それ自身としか等しくない。

useCallbackの目的

const MyComponent = () => {
  // handleClick is re-created on each render
  const handleClick = () => {
    console.log('Clicked!');
  };

  // ...
}

このhandleClick関数は、コンポーネントが再レンダリングされるたびに再生成されます。
そのため、レンダリングごとに異なるオブジェクトになります。

インライン機能は安価な(軽い?)なので、レンダリングごとに機能を作り直すことは問題になりません。
コンポーネントごとに数個のインライン関数があれば問題ありません。

※インライン関数とは、名前のついた無名関数のこと。たとえば以下のような関数のこと。

const handleClick = () => {
    console.log('Clicked!');
  };

しかし、場合によってはレンダリング間で1つの関数インスタンスを維持しておく必要があります。

  1. React.memo()でラップされた機能コンポーネントが、関数オブジェクトpropを受けとっている場合。
  2. useEffect(..., [callback])のように、関数オブジェクトが他のフックに依存している場合。
  3. 関数が何らかの内部状態を持っているとき、例えば関数がデバウンスやスロットルされているとき。

useCallback(callbackFun, deps)が役に立つのは以上3つのとき。
同じ依存関係の値(deps)が与えられると、hookはレンダリングの間に関数インスタンスを返す。

import { useCallback } from 'react';

const MyComponent = () => {
  // handleClick is the same function object
  const handleClick = useCallback(() => {
    console.log('Clicked!');
  }, []);

  // ...
}

handleClickは、MyComponentがレンダリングされる間、常に同じコールバック関数オブジェクトを保持するようになります。

良い使い方

例えば、とても大量のitemリストをレンダリングするコンポーネントがあったとします。

import useSearch from './fetch-items';

const MyBigList = ({ term, onItemClick }) => {
  const items = useSearch(term);

  const map = item => <div onClick={onItemClick}>{item}</div>;

  return <div>{items.map(map)}</div>;
}

export default React.memo(MyBigList);

リストはとても大きく、数百のアイテムがあるかもしれません。無駄なリストの再レンダリングを防ぐために、React.memo()でラップしています。

import { useCallback } from 'react';

export default const MyParent = ({ term }) => {
  const onItemClick = useCallback(event => {
    console.log('You clicked ', event.currentTarget);
  }, [term]);

  return (
    <MyBigList
      term={term}
      onItemClick={onItemClick}
    />
  );
}

onItemClickuseCallbackにてラップしています。
useCallback(callbackFun, term)termが同じであれば、useCallbackは同じ関数オブジェクトを返します。

このような使い方をすれば、MyParentコンポーネントが再レンダリングされても、onItemClick関数オブジェクトは同じままで、MyBigListのメモ化が壊れることはありません。

悪い使い方

import { useCallback } from 'react';

const MyComponent = () => {
  // Contrived use of `useCallback()`
  const handleClick = useCallback(() => {
    // handle the click event
  }, []);

  return <MyChild onClick={handleClick} />;
}

const MyChild = ({ onClick }) => {
  return <button onClick={onClick}>I am a child</button>;
}

このコンポーネントでは、useCallbackを使う意味があるのでしょうか?
MyChildコンポーネントは軽く、その再レンダリングはパフォーマンスの問題を引き起こさないのでほとんどの場合には意味がないと思ってよいです。

useCallbackフックは、MyComponentがレンダリングされるたびに呼びされます。
useCallbackが同じ関数オブジェクトを返したとしても、再レンダリングのたびにインライン関数が再作成されます。(useCallbackはそのインライン関数を返すことをスキップするだけです。)

また、useCallbackを使うことで、コードの複雑性も増します。useCallback(..., deps)のdepsをメモライズされたコールバックの内部で使用しているものと同期させておく必要があります。

結論として、最適化は、最適化を行わないよりもコストがかかることになります。
このような場合には、新しい関数が作成されることを許容するべきです。

まとめ

最適化を行うと、複雑さが増します。
最適化をするのが早すぎると、最適化されたコードが何度も変更される可能性があるため、リスクを伴います。

useCallbackを適切に使用するタイミングは、メモライズされた重たい子コンポーネントに供給されるコールバック関数をメモ化することです。
また、パフォーマンスの向上を数値化し、そのパフォーマンスの向上は複雑さの増加と比較して、useCallbackを使う価値があるかを判断しましょう。

#紹介
私が所属している「もりけん塾」は、もりけん先生が運営するJavaScriptに特化したコミュニティーです。(とは言ってもいろんな方がいらっしゃいます。)
フロントエンドエンジニアになりたい方にむけて、先生が道標となって初学者が迷わないように導いてくれます。コードの書き方、自走力を身に着けるにはとても良い環境です。
毎月1日に募集をかけていたのですが、ここ最近は募集かけなくても入塾したいとDMがくるそうです...!!(大人気)

先生のブログ
先生のTwitter

19
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?