LoginSignup
1
0

More than 1 year has passed since last update.

基礎から学ぶReact/React Hooks学習メモ 5-3 useCallback React.memo useMemo

Posted at

React Hooksを基礎から理解する 5-3 useCallback React.memo useMemo

参考
- 基礎から学ぶReact/React Hooks

  • useCallback()、useMemo()は、パフォーマンスチューニングのためのフック
  • コンポーネントの不要な再レンダリングを防ぐ
  • Reactコンポーネントでは、state、propsが更新されるたびに再レンダリングが行われる
  • useCallback()、React.memo()、useMemo()は、前回の差分がないことが事前に分かっている場合、再レンダリングをスキップできる
  • useCallback()は、React.memo()と組み合わせて使用する
  • メモ化とは
    • 同じ結果を返す処理について、初回のみ処理を実行して記録
    • 2回目以降は、保持していた結果を再利用する

React.memoとは

  • コンポーネントのレンダリング結果をメモ化するReact API
  • 親コンポーネントから渡されるpropsについて、変更差分をチェックして、コンポーネントが返した描画結果を記録してメモ化し、差分があった倍の三再レンダリングする
    • propsの前後を比較し、等価かどうかチェック
    • 等価であれば、メモ化したコンポーネントを利用するので、再レンダリングをスキップ
    • 等価でなければ、再レンダリングする
  • React.memo()でラップしていても、コンポーネント内部でuseState()やuseContext()を使用している場合、その変化に応じた再レンダリングが発生する
  • useMemo()の基本構文
React.memo(親コンポーネントからpropsを受け取る子コンポーネント)
  • React.memo()の利用例

image.png

  • React.memo()未使用時(AボタンとBボタンを1回ずつ押す)

image.png

  • React.memo()使用時(AボタンとBボタンを1回ずつ押す)

image.png

import React, { useState } from "react";
import "./styles.css";

// React.memo()で関数全体を括る。propsが同じときはメモ化したコンポーネントを使用する
const CountResult = React.memo(({ text, countState }) => {
  console.log(`${text}ボタンがクリックされました!`);
  return (
    <p>
      {text}: {countState}
    </p>
  );
});

const Counter = () => {
  const [countStateA, setCountStateA] = useState(0);
  const [countStateB, setCountStateB] = useState(0);

  const countIncrementA = () => {
    setCountStateA((prevCount) => prevCount + 1);
  };
  const countIncrementB = () => {
    setCountStateB((prevCount) => prevCount + 1);
  };

  return (
    <>
      <CountResult text="Aボタン" countState={countStateA} />
      <CountResult text="Bボタン" countState={countStateB} />

      <button onClick={countIncrementA}>Aボタン</button>
      <button onClick={countIncrementB}>Bボタン</button>
    </>
  );
};

export default function App() {
  return <Counter />;
}

useCallbackとは

  • React.memoを使っても親コンポーネントから、「コールバック関数」をpropsとして受け取った場合、子コンポーネントは再レンダリングされてしまう。
  • 関数自体のメモ化に利用できるのが、メモ化されたコールバック関数を返すuseCallback()
  • インポート
import React, { useCallback } from "react";
  • useCallback()の基本構文
useCallback(コールバック関数, [コールバック関数が依存している要素の配列]);
  • a,bどちらも変化しなければ、メモ化した関数を再利用する
// メモ化したいsampleCallbackFunc関数を宣言
const sampleCallbackFunc = () => doSomething(a, b);
// useCallbackでメモ化したい、sampleCallbackFunc関数をラップ
const memorizedSampleCallbackFunc = useCallback(sampleCallbackFunc, [a, b]);
  • 配列を空にすると、初回レンダリング時に生成した関数をずっと使い続ける
const memorizedSampleCallbackFunc = useCallback(sampleCallbackFunc, []);
  • useCallbackの利用例
    • useCallback()は、React.memo()と組み合わせて使用する
    • useCallback()で使用する配列の項目は、関数内で使われてないといけない。逆に使われていないときはその項目はいらない。
    • setCountStateA(countStateA + 1) を、setCountStateA((prevCount) => prevCount + 1) とすれば、[]で良い

image.png

  • Aボタン、Bボタンを1回ずつ押したとき、異なるボタンのログが出ていない。

image.png

import React, { useState, useCallback } from "react";
import "./styles.css";

// useCallback()は、React.memo()と組み合わせて使用する
const Button = React.memo(({ counterState, buttonValue }) => {
  console.log(`${buttonValue}がクリックされました!`);
  return <button onClick={counterState}>{buttonValue}</button>;
});

const Counter = () => {
  const [countStateA, setCountStateA] = useState(0);
  const [countStateB, setCountStateB] = useState(0);

  // 関数をメモ化する
  const countIncrementA = useCallback(() => {
    setCountStateA(countStateA + 1);
  }, [countStateA]);
  // 関数をメモ化する
  const countIncrementB = useCallback(() => {
    setCountStateB(countStateB + 1);
  }, [countStateB]);

  return (
    <>
      <p>Aボタン: {countStateA}</p>
      <p>Bボタン: {countStateB}</p>
      <Button counterState={countIncrementA} buttonValue="Aボタン" />
      <Button counterState={countIncrementB} buttonValue="Bボタン" />
    </>
  );
};

export default function App() {
  return <Counter />;
}

useMemo

  • 関数の結果を保持する
  • 何度計算しても結果が同じ場合の、「関数による計算の結果」をメモ化し、そこから値を取得する
  • useCallback() → 関数自体をメモ化する
  • useMemo() → 関数の結果を保持する
  • インポート
import React, { useMemo } from "react";
  • useMemo()の基本構文
useMemo(() => 値を計算する関数の呼び出し, [値の計算に必要な要素の配列]);
  • a,b のどちらかに変更があった場合、関数を再実行する。
useMemo(() => doSomething(a, b), [a, b]);
  • 第二引数を空とした場合、初回の一度のみ実行され、それ以降はキャッシュから値を取得する
useMemo(() => doSomething(a, b), []);
  • useMemoの利用例
    • Bの計算がとても重いプログラム

image.png

  • useMemo()を使用したとき(A,Bボタンを1回ずつクリック。重い処理が1度実行)

image.png

  • useMemo()を使用しないとき(A,Bボタンを1回ずつクリック。重い処理が2度実行)

image.png

import React, { useState, useMemo } from "react";
import "./styles.css";

const square = (param) => {
  const testData = [...Array(1000).keys()];

  testData.forEach(() => {
    console.log(
      `「計算: B + 1」がボタンクリックされ、square関数実行、ループ処理を${testData.length}回実行中...`
    );
  });
  return param * param;
};

const Counter = () => {
  const [countStateA, setCountStateA] = useState(0);
  const [countStateB, setCountStateB] = useState(0);

  // squreがとても重い処理なので、countStateBが変化した時だけ実行してほしい
  const squareArea = useMemo(() => square(countStateB), [countStateB]);
  // ラップしないとき
  // const squareArea = square(countStateB);

  const countIncrementA = () => {
    setCountStateA((prevCount) => prevCount + 1);
    console.log("計算: A + 1ボタンがクリックされました!");
  };
  const countIncrementB = () => {
    setCountStateB((prevCount) => prevCount + 1);
    console.log("計算: B + 1ボタンがクリックされました!");
  };

  return (
    <>
      <p>
        計算結果A: {countStateA}
        <button onClick={countIncrementA}>計算: A + 1</button>
      </p>
      <p>
        計算結果B: {countStateB}
        <button onClick={countIncrementB}>計算: B + 1</button>
      </p>
      <p>正方形の面積</p>
      <p>計算結果B × 計算結果B = {squareArea}</p>
    </>
  );
};

export default function App() {
  return <Counter />;
}
1
0
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
1
0