1. ホーム
  2. 記事一覧
  3. Reactの基本、useCallbackを理解しよう

2024.10.28

Reactの基本、useCallbackを理解しよう

はじめに

Reactは、ユーザーインターフェースを作るためのJavaScriptライブラリで、ウェブやモバイルアプリの開発で広く利用されています。Reactを学ぶことで、モダンなウェブ開発スキルが身につき、フロントエンドエンジニアを目指す方にとって重要な技術です。

Reactを学ぶ過程で、React.memo、useMemo、そしてuseCallbackに触れたことがある方もいるでしょう。これらはReactでパフォーマンスを向上させるためのツールで、それぞれに異なる役割と使用場面があります。

フロントエンド初学者の方にとっては、これらの仕組みは少し難しく感じるかもしれませんが、Reactプロジェクトのパフォーマンス向上に役立つので、理解しておくと便利です。

この記事では、その3つの中からuseCallbackに焦点を当て、詳しく解説します。useCallbackは、関数をメモ化し、再レンダリング時に不必要な関数の再作成を避けることができる仕組みです。それでは、一緒に学んでいきましょう。

この記事について

目的

  • useCallbackの仕組みを理解し、使えるようになること

対象者

  • Reactの学習をしている方
  • useCallbackの理解を深めたい方

Reactの基本シリーズ

Reactを学んでいる中で、「なかなか理解が進まない…」と感じている方もいるのではないでしょうか?そんな方に向けて、Reactの基本をわかりやすく紹介するシリーズ記事を用意しています。

以下はシリーズの一覧です。この記事とあわせて読むことで、より理解が深まると思いますので、ぜひ参考にしてみてください。

React useCallbackについて

useCallbackは、Reactで関数の再生成を制御し、不要な再レンダリングを防ぐためのフックです。コンポーネントが再レンダリングされると、その中で定義した関数も再生成されますが、useCallbackを使うと特定の依存関係が変更されたときだけ関数が新しく作られます。

この機能は特に、再生成された関数が子コンポーネントに渡される場合に便利です。不要な再レンダリングを避けることで、Reactのパフォーマンスが向上します。

プログラミングにおけるメモ化(memoization)とは

メモ化とは、一度計算した結果を保存し、同じ入力で呼び出されたときに再計算せずに保存された結果を返す手法です。これにより不要な計算を減らして処理を効率化できます。

例えば、ある計算をする関数が引数に基づいて結果を返す場合、最初の呼び出しでその結果を保存しておき、次回以降は同じ引数なら計算せずに保存した結果を返します。これがメモ化の基本的な考え方です。

useCallbackの仕組み

useCallbackは、メモ化の考え方をReactの関数に応用しています。useCallbackを使うと、依存配列に指定した値が変わらない限りReactは関数を再生成せず、前回の関数をそのまま利用します。これにより、不要な関数の再作成を防ぎ、処理が効率化されます。

通常、コンポーネントが再レンダリングされると、その中で定義した関数もすべて再生成されます。同じように見える関数でも、Reactは毎回新しい関数として認識します。特に、関数を子コンポーネントに渡している場合に、Reactは「新しい関数」と判断して子コンポーネントを再レンダリングしてしまいます。このような場合にuseCallbackを使うと、毎回同じ関数を再生成する必要がなくなり、パフォーマンスが向上します。

useCallbackの使用目的

useCallbackは、主に以下のような場面で役立ちます

  • パフォーマンスの向上

    useCallbackは、必要なタイミングでのみ関数を再生成することで、コンポーネントの不要な再レンダリングを減らし、アプリ全体のパフォーマンス向上に役立ちます。特に、関数をpropsとして頻繁に更新される子コンポーネントに渡す場面で効果的です。

  • 関数の参照の一貫性を保つ

    子コンポーネントに関数を渡すとき、その関数が新しくなると、Reactは子コンポーネントを「変化があった」と見なして再レンダリングしてしまいます。useCallbackで関数を固定することで、子コンポーネントが安定し、不要な再レンダリングを防げます。

  • ボタンのクリックなどの動作を効率化

    例えば、ボタンをクリックしたときに呼び出される関数をuseCallbackで固定すると、Reactは同じ関数を再利用できます。これにより、毎回関数を作り直す必要がなくなり、アプリの動作がスムーズになります。

useCallbackの使用に関する注意点

  • 必要なときだけ使う

    useCallbackはパフォーマンスの最適化に役立ちますが、すべての関数に対して使用する必要はありません。関数を再生成しても問題がない軽量なコンポーネントでは、useCallbackを使うことでかえって処理が増え、逆にパフォーマンスが低下することもあります。

  • 依存配列を正しく指定する

    useCallbackで関数を再利用するかは、依存配列に指定した値が変化したかどうかで決まります。例えば、関数内で使用する変数が依存配列に含まれていない場合、その変数が変わっても関数は再生成されないため、古い値が使われ続けてしまいます。依存配列に必要な変数を正しく含めることが重要です。

依存配列の役割

これまでのセクションでお伝えした通り、依存配列は関数を再生成するかどうかを判断するための基準です。

依存配列には空の配列を指定することも可能です。この場合、コンポーネントが初めて表示されたときのみ関数が生成され、その後は依存配列の値が変わらないため、関数は更新されません。これにより、再レンダリング時も同じ関数を再利用するので、パフォーマンスが向上します。

関数が再作成されるとは

Reactのコンポーネントは、関数(関数コンポーネント)やクラス(クラスコンポーネント)によって定義されます。関数コンポーネントの場合、コンポーネントが再レンダリングされると、その関数がもう一度呼び出され、結果として中に書かれているコード(変数や関数を含む)が再実行されます。たとえば、以下のようなコンポーネントを考えてみましょう。

function MyComponent() {
  const handleClick = () => {
    console.log("Clicked!");
  };

  return <button onClick={handleClick}>Click Me</button>;
}

このMyComponentが再レンダリングされると、handleClickも新しく作られます。JavaScriptでは、関数が呼び出されるとその内部の変数や関数も毎回新しく作られる仕組みのため、Reactの関数コンポーネントでも同様に再作成が行われます。

使い方と具体的な仕組み

useCallbackを使用するには、コンポーネントの冒頭でuseCallbackをインポートし、メモ化したい関数をラップします。これにより、指定した関数が効率的に再利用されるようになります。

import { useCallback } from 'react';

const memoFuncton = useCallback(メモ化したい関数, [依存配列])

もう少し具体的なコード例を見ていきます。

import { useCallback } from 'react';

const MyComponent = () => {
  const memoFunction = useCallback(() => {
    console.log('関数が実行されました');
  }, []);
};

このコードでは、useCallbackを使ってmemoFunctionという関数を定義しています。

useCallbackでラップされたmemoFunctionは、依存配列が空([])なので、コンポーネントが初めて表示されたときにだけ作られます。その後、コンポーネントが何度再レンダリングされても、memoFunctionは新しく作り直されず、最初に作られたものが使われ続けます。

useCallbackを使用したコンポーネント例

ここまででuseCallbackの基本を学びました。このセクションでは、useCallbackをReact.memoと併用した例を紹介します。useCallbackは、React.memoと組み合わせることで、その効果がより発揮されます。それでは、実際にどのようにuseCallbackを使うか見ていきましょう。

// App.jsx

import React, { useState, useCallback } from 'react';

// 親コンポーネント
export default function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  // useCallbackで関数をメモ化する
  const handleClick = useCallback(() => {
    setCount((count) => count + 1);
  }, []); //空の依存配列を渡している
  console.log('親コンポーネントがレンダリングされました');

  return (
    <div>
      <h2>Parent Component</h2>
      <button onClick={handleClick}>Count: {count}</button>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Type something..."
      />
      <ChildComponent onClick={handleClick} />
    </div>
  );
}

// 子コンポーネント
// React.memoで不要な再レンダリングを防ぐ
const ChildComponent = React.memo(({ onClick }) => {
  console.log('子コンポーネントがレンダリングされました');

  return (
    <div>
      <h3>Child Component</h3>
      <button onClick={onClick}>Increment from Child</button>
    </div>
  );
});

このコード例は以下のようなコンポーネントが作成できます。

コードの解説

このコードは、以下のように動作します。

  • useCallbackの役割

    今回のコードでは、useCallbackを使用してhandleClick関数をメモ化しています。依存配列を空にしているため、この関数は最初の生成後、常に同じ参照が利用されます。これにより、親コンポーネントが再レンダリングされても、handleClickの参照は変わらず、効率的に再利用されます。

  • React.memoと併用する効果

    さらに、ここではReact.memoを使って子コンポーネントをラップしています。React.memoは、渡されたpropsに変更がない場合に、子コンポーネントの再レンダリングをスキップする仕組みです。通常、親コンポーネントが再レンダリングされると、子コンポーネントも再レンダリングされます。今回の例では、useCallbackでhandleClickの参照が固定されているため、React.memoの効果により「propsは同じ」を判断され、子コンポーネントのレンダリングをスキップします。

なぜReact.memoが必要なのか

useCallbackでhandleClick関数をメモ化しても、React.memoでラップしていなければ、親コンポーネントが再レンダリングされるたびに子コンポーネントも再レンダリングされてしまいます。これは、Reactのデフォルトの挙動が、親コンポーネントの再レンダリングに合わせて子コンポーネントも再レンダリングするという仕様になっているためです。

useCallbackを使うとhandleClick関数の参照は保持されますが、それだけでは親の再レンダリングに伴う子コンポーネントの連動を防ぐことはできません。つまり、useCallbackは関数の参照を固定するだけで、親と子のレンダリングが連動している状態には影響を与えません。

そこでReact.memoが役立ちます。React.memoでChildComponentをラップすることで、親コンポーネントが再レンダリングされても、props(今回の場合はonClickプロパティ)の参照が同じであれば子コンポーネントのレンダリングをスキップできます。ReactはhandleClickの参照が変わらないと判断し、不要なレンダリングを避けることができます。

この記事で学んだこと

この記事を通して、useCallbackについて学びました。最後に大切なポイントを振り返りましょう。

  • useCallbackの仕組み

    useCallbackは、関数をメモ化し、再レンダリング時に不必要な関数の再作成を避けることができる仕組みです。特定の依存関係が変わらない限り関数を再生成せず、以前に作成した関数をそのまま再利用します。これにより、不要な関数の再作成を防ぎ、処理が効率化されます。

  • useCallbackの使用目的

    useCallbackは、主に不要な再レンダリングを減らし、コンポーネントのパフォーマンスを向上させるために使用します。特に、頻繁に再生成される関数を子コンポーネントに渡す場面で効果的です。

  • React.memoとの併用による効果

    React.memoは、親コンポーネントが再レンダリングされても、渡されたpropsが変わらない限り子コンポーネントのレンダリングをスキップする仕組みです。

    useCallbackとReact.memoを併用することは、特に親コンポーネントの関数を子コンポーネントのpropsとして渡している際に効果を発揮します。子コンポーネントをメモ化することで、親コンポーネントが再レンダリングされても、propsとして渡している関数の参照が同じであれば、子コンポーネントのレンダリングをスキップできます。これにより、Reactはメモ化された関数の参照が変わらないと判断し、不要なレンダリングを避けることができます。

参考資料

以下のリンクは、この記事で説明した手順や概念に関連する参考資料です。より詳しく学びたい方は、ぜひご参照ください。

【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

IT未経験者必見 USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。

「フリーランスエンジニア」

近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。

「成功する人とそうでない人の違いは何か?」

私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。

比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。

多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、

note記事3000いいね超えの殿堂記事 今すぐ読む

エンベーダー編集部

エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。

RareTECH 無料体験授業開催中! オンラインにて実施中! Top10%のエンジニアになる秘訣を伝授します! RareTECH講師への質疑応答可

関連記事