designsupply.llc
  • aboutabout
  • servicesservices
  • worksworks
  • mediamedia
  • contactcontact
  • homeサイトトップ
  • about会社概要
  • services業務内容
  • works実績紹介
  • mediaメディア
  • contactお問い合わせ
ja/

media

メディア

  1. サイトトップ
  2. メディア
  3. プログラミングの記事
  4. Reactで値や関数をメモ化してパフォーマンスを改善してみる(React.memo, useMemo, useCallback)

Reactで値や関数をメモ化してパフォーマンスを改善してみる(React.memo, useMemo, useCallback)

  • 2022.06.17
  • プログラミング
  • React
Updated: 2022.06.18
Reactでは親コンポーネントのstateやpropsの値などが更新されると、それに紐づく子コンポーネントがレンダリングされてしまいます。もちろん、子コンポーネント側で受け取る場合には再レンダリングされることで、コンポーネントの情報も新しく更新されるのですが、関係のないコンポーネントまでレンダリングされるため、大量のコンポーネントを読み込んでいる場合では、パフォーマンスに影響が出てしまいます。そうならないようなコンポーネント設計も重要ですが、Reactでは「React.memo」「useMemo」「useCallback」という、コンポーネントや処理などを「メモ化」する機能が用意されています。Reactでの開発はこれらの活用が欠かせません、今回はサンプルと合わせて使い分けや違いについて改めてチェックしてみたいと思います。   Reactのメモ化というのは、処理や値を再利用できるような形で保持するような機能で、個人的にはキャッシュのようなイメージと捉えています。メモ化をすることで、重い計算処理を無駄に何度も繰り返すことや、不要なコンポーネントのレンダリングを防ぐなどの役割を果たします。つまり、重たくなりがちな場面でのパフォーマンスが期待できるようになります。    
React.memoで親要素のレンダリングに伴う子コンポーネントの不要なレンダリングを防ぐ
似たよう機能と名前でややこしいのですが、まずは「React.memo」を使ってみます。React.memoはコンポーネント自体をメモ化するもので、主に不要なレンダリングを避けたい子コンポーネントに対して適用していきます。   まずは適用前のサンプルを見ていきます。今回は親コンポーネントに、チェックボックスとレンジのinput要素を用意し、値を子コンポーネント側で表示するというようなものになります。親コンポーネント側では「useState」でインプット要素の値が動的に変化できるようにしておきます。そして、レンジの値を子コンポーネント側にPropsとして受け渡します。 【ParentComponent.tsx】
import React from 'react';
import { ChildComponent } from './ChildComponent';
export const ParentComponent: React.VFC = (): JSX.Element => {
  const [currentRange, setRange] = useState(0);
  const [isChecked, setCheck] = useState(false);
  const toggleCheck = (): void => {
    setCheck(!isChecked);
  };
  const updateRange = (event: React.ChangeEvent<HTMLInputElement>): void => {
    setRange(Number(event.target.value));
  };
  console.log('ParentComponent rendered!');
  return (
    <>
      <label style={{display: 'block'}}>
        <input type="checkbox" onChange={() => {toggleCheck()}} />
      </label>
      <label style={{display: 'block'}}>
        <input type="range" min="0" max="100" step="10" defaultValue={0} onChange={(event) => updateRange(event)} />
      </label>
      <ChildComponent data={currentRange} />
    </>
  );
}
  子コンポーネントでは、親コンポーネントからPropsで受け取ったレンジの値を出力するというような形です。ここではレンダリングされたことがわかるようにコンソールにログが表示されるようにしておきます。親コンポーネント側でレンジの値が変更されるたびに、子コンポーネント側で値が更新されるような感じですね。 【ChildComponent.tsx】
import React from 'react';
export type childProps = {
  data: number
};
export const ChildComponent: React.VFC<childProps> = (props): JSX.Element => {
  console.log('ChildComponent rendered!');
  return (
    <>
      <div>値:{props.data}</div>
    </>
  );
};
  レンジの値を切り替えると子コンポーネントがレンダリングされるのは必要になりますが、上記の場合、子コンポーネントとは関係のないチェックボックスの値が更新された時でも子コンポーネントがレンダリングされてしまいます。親コンポーネント側で保持するstateの値が更新されたためですね。コンソールでは下記にように子コンポーネントがレンダリングされているのが確認できますね。
// console(チェックボックスを切り替えたとき)
ParentComponent rendered!
ChildComponent rendered!
  コンポーネントの構造が複雑だったり数が多い場合には、このようなレンダリングが原因で動作や表示が重たくなることもあります。そんな場合には、子コンポーネントに「React.memo」を使って、コンポーネント自体をメモ化することで不要なレンダリングを防ぐことができます。関数コンポーネントの場合ですと、引数にそのままコンポーネントの関数が入る形になります。 【ChildComponent.tsx】※一部抜粋
export const ChildComponent: React.VFC<childProps> = React.memo((props): JSX.Element => {
  console.log('ChildComponent rendered!');
  return (
    <>
      <div>値:{props.data}</div>
    </>
  );
});
  こうすることで、コンポーネントでPropsとして受け取らない値が更新された場合には、子コンポーネントがレンダリングされないようになります。コンソールでも確認することができました。
// console(チェックボックスを切り替えたとき)
ParentComponent rendered!
   
React.memoとuseCallbackでPropsで受渡す関数をメモ化して子コンポーネントの不要なレンダリングを防ぐ
子コンポーネントには値だけでなく関数もPropsとして受け渡すこともあります。下記の例では、レンジのインプット要素とレンジの値を取得して更新する処理を子コンポーネント側に持たせるパターンです。そのため、親コンポーネント側で定義している関数をPropsとして子コンポーネントに受け渡しています。また、今回のようにuseCallbackを使う場合にはモジュールからインポートしておきます。 【ParentComponent.tsx】
import React from 'react';
import { useCallback, useState } from 'react';
import { ChildComponent } from './ChildComponent';
export const ParentComponent: React.VFC = (): JSX.Element => {
  const [currentRange, setRange] = useState(0);
  const [isChecked, setCheck] = useState(false);
  const toggleCheck = (): void => {
    setCheck(!isChecked);
  };
  const updateRange = (event: React.ChangeEvent<HTMLInputElement>): void => {
    setRange(Number(event.target.value));
  };
  console.log('ParentComponent rendered!');
  return (
    <>
      <label style={{display: 'block'}}>
        <input type="checkbox" onChange={() => {toggleCheck()}} />
      </label>
      <ChildComponent
        data={currentRange}
        methods={updateRange}
      />
    </>
  );
}
  子コンポーネント側では、受け取った関数をコンポーネント内で使われるインプット要素のイベントで実行されるように使います。これで、子コンポーネントでのイベントで値を受け取り、その値が親コンポーネント側のstateで更新されるようになります。 【ChildComponent.tsx】
import React from 'react';
export type childProps = {
  data: number,
  methods: (event: React.ChangeEvent<HTMLInputElement>) => void
};
export const ChildComponent: React.VFC<childProps> = React.memo((props): JSX.Element => {
  console.log('ChildComponent rendered!');
  return (
    <>
      <label style={{display: 'block'}}>
        <input type="range" min="0" max="100" step="10" defaultValue={0} onChange={(event) => props.methods(event)} />
      </label>
      <div>値:{props.data}</div>
    </>
  );
});
  ただし、先ほどと同様に親コンポーネント側で子コンポーネントに関与しないstateの値が更新された場合に、React.memoでコンポーネント自体をメモ化していても、関数自体は親コンポーネントで定義しているため、子コンポーネントがレンダリングされてしまいます。
// console(チェックボックスを切り替えたとき)
ParentComponent rendered!
ChildComponent rendered!
  このように、子コンポーネントに関数を受け渡す際に活用できるのが「useCallback」です。useCallbackは関数をメモ化することで、対象とする値が更新された時にしか実行しないように処理を制御することができ、不要な場面で処理が実行されないようにします。useCallbackでは第一引数に対象とする処理を、第二引数に処理を実行する対象となる配列値を指定します。第二引数で指定した値が更新された時にしか、第一引数の処理が実行されないようになります。
useCallback( メモ化する対象の処理, 更新対象の値 )
  実際には下記のようになります。親コンポーネント側で定義されているので、useCallbackの引数にメモ化する処理を指定し、第二引数で更新の対象となるstateの値を指定します。これで第二引数で指定した値が更新されない限り、第一引数の処理が実行されなくなります。 【ParentComponent.tsx】※一部抜粋
export const ParentComponent: React.VFC = (): JSX.Element => {
  const [currentRange, setRange] = useState(0);
  const [isChecked, setCheck] = useState(false);
  const toggleCheck = (): void => {
    setCheck(!isChecked);
  };
  const updateRange = useCallback((event: React.ChangeEvent<HTMLInputElement>): void => {
    setRange(Number(event.target.value));
  }, [currentRange]);
  console.log('ParentComponent rendered!');
  return (
    <>
      <label style={{display: 'block'}}>
        <input type="checkbox" onChange={() => {toggleCheck()}} />
      </label>
      <ChildComponent
        data={currentRange}
        methods={updateRange}
      />
    </>
  );
}
  実際にPropsで受け渡していない値が変更された時には子コンポーネントがレンダリングされていないことが確認できます。ただし、注意点としてuseCallbackを使う際には、受け渡す側の子コンポーネントで、React.memoなどでコンポーネント自体もメモ化しておく必要があります。
// console(チェックボックスを切り替えたとき)
ParentComponent rendered!
   
React.memoとuseMemoで値を返す関数の再計算と紐づく子コンポーネントのレンダリングを防ぐ
コンポーネント内では、受け取った値や受け渡す値を計算して、HTML上に出力することもあります。軽い処理では問題ありませんが、重い処理がたくさん実行されるような場合には、不要なレンダリングで処理が再計算されることが原因でパフォーマンスにも影響が出てしまいます。受け渡す値がそのような処理で算出された値の場合には、できるだけ不要なレンダリングを避けたいですね。下記のサンプルでは、親コンポーネント側で値を加算、そして乗算するという異なる処理が行われるパターンになります。そして、それぞれの処理で算出された値は異なる子コンポーネントに受け渡すというような構成です。 【ParentComponent.tsx】
import React from 'react';
import { useMemo, useState } from 'react';
import { ResultPlus } from './ResultPlus';
import { ResultMultiplie } from './ResultMultiplie';
export const ParentComponent: React.VFC = (): JSX.Element => {
  const [currentRangePlus, setRangePlus] = useState(0);
  const [currentRangeMultiplie, setRangeMultiplie] = useState(0);
  const updateRangePlus = (event: React.ChangeEvent<HTMLInputElement>): void => {
    setRangePlus(Number(event.target.value));
  };
  const updateRangeMultiplie = (event: React.ChangeEvent<HTMLInputElement>): void => {
    setRangeMultiplie(Number(event.target.value));
  };
  const calculatePlus = (): number => {
    console.log('加算')
    return currentRangePlus + 10;
  };
  const calculateMultiplie = (): number => {
    console.log('乗算')
    return currentRangeMultiplie * 10;
  };
  return (
    <>
      <label style={{display: 'block'}}>
        <input type="range" min="0" max="100" step="10" defaultValue={0} onChange={(event) => updateRangePlus(event)} />
      </label>
      <label style={{display: 'block'}}>
        <input type="range" min="0" max="100" step="10" defaultValue={0} onChange={(event) => updateRangeMultiplie(event)} />
      </label>
      <ResultPlus data={calculatePlus} />
      <ResultMultiplie data={calculateMultiplie} />
    </>
  );
}
  子コンポーネントでは、受け取った値をHTML上に出力しています。親コンポーネント側で定義されている計算処理と子コンポーネント側では、処理の実行やコンポーネントのレンダリングなどがわかるようコンソールに出力されるようにしています。
// ResultPlus.tsx
import React from 'react';
export type childProps = {
  data: () => number
};
export const ResultPlus: React.VFC<childProps> = React.memo((props): JSX.Element => {
  console.log('ResultPlus rendered!');
  return (
    <>
      <div>+10:{props.data()}</div>
    </>
  )
});

// ResultMultiplie.tsx
import React from 'react';
export type childProps = {
  data: () => number
};
export const ResultMultiplie: React.VFC<childProps> = React.memo((props): JSX.Element => {
  console.log('ResultMultiplie rendered!');
  return (
    <>
      <div>*10:{props.data()}</div>
    </>
  )
});
  本来ですと、それぞれのコンポーネントに紐づく値が更新されたものだけ対応するコンポーネントのレンダリングや処理が実行されてほしいところですが、両方ともの処理が実行されてしまっているのがコンソールで確認できます。
// console(加算処理用のレンジを操作した時)
ResultPlus rendered!
加算
ResultMultiplie rendered!
乗算
  そこで「useMemo」を使って各計算処理で算出された値をメモ化しておきます。先ほどのuseCallbackと同じ要領で、第一引数には処理を、第二引数には更新対象になる配列値を指定しておきます。
useMemo( メモ化する対象の処理, 更新対象の値 )
  第二引数で指定した値が更新されない限りは再計算されないという形になります。先ほどの親コンポーネントに対して下記のように対象となる処理にuseMemoを適用していきます。第二引数にはそれぞれ紐づくstateの値を指定します。 【ParentComponent.tsx】※一部抜粋
export const ParentComponent: React.VFC = (): JSX.Element => {
  const [currentRangePlus, setRangePlus] = useState(0);
  const [currentRangeMultiplie, setRangeMultiplie] = useState(0);
  const updateRangePlus = (event: React.ChangeEvent<HTMLInputElement>): void => {
    setRangePlus(Number(event.target.value));
  };
  const updateRangeMultiplie = (event: React.ChangeEvent<HTMLInputElement>): void => {
    setRangeMultiplie(Number(event.target.value));
  };
  const calculatePlus = useMemo(() => (): number => {
    console.log('加算')
    return currentRangePlus + 10;
  }, [currentRangePlus]);
  const calculateMultiplie = useMemo(() => (): number => {
    console.log('乗算')
    return currentRangeMultiplie * 10;
  }, [currentRangeMultiplie]);
  return (
    <>
      <label style={{display: 'block'}}>
        <input type="range" min="0" max="100" step="10" defaultValue={0} onChange={(event) => updateRangePlus(event)} />
      </label>
      <label style={{display: 'block'}}>
        <input type="range" min="0" max="100" step="10" defaultValue={0} onChange={(event) => updateRangeMultiplie(event)} />
      </label>
      <ResultPlus data={calculatePlus} />
      <ResultMultiplie data={calculateMultiplie} />
    </>
  );
}
  これでそれぞれの処理で算出された値がメモ化され、更新されない場合には再利用される形になります。処理が再計算されたり、値の受け渡し先の子コンポーネントもレンダリングされていないことがコンソールで確認できます。
// console(加算処理用のレンジを操作した時)
ResultPlus rendered!
加算
  これらのパフォーマンス改善に活用できる各種メソッドをまとめてみると下記のようになります。  
React.memo コンポーネントをメモ化して不要なレンダリングを防ぐ、コンポーネント自体に適用する
useCallback 関数をメモ化して処理自体を再利用できるようにする、Propsで受け渡す関数などに適用する
useMemo 返り値をメモ化して、処理の再計算を防いだり値を再利用できるようにする、Propsで受け渡す値などに適用する
 
  似ているようですが、それぞれ適切に扱うことで処理のパフォーマンスを改善することが期待できますので、普段はVue.jsを使うことが多いのですが、Reactでのアプリケーション開発ではぜひ覚えておきたいですね。

この記事を書いた人オガワ シンヤ

合同会社デザインサプライ代表兼CEO / ディレクター・ウェブデザイナー・フロントエンドエンジニアをやっています。「ウェブとデザインでヒト・モノ・サービスを繋げ新しい価値を生み出す」をコンセプトに日々奮闘中!制作中はダイエットコーラとチョコレートが欠かせないアラフォーWebクリエイター。

Sponsored Link

  • Showcase
  • Startify UI
  • Functional Gradation Generator
  • Coloooors

categories

  • WordPress(61)
  • JavaScript(49)
  • Vue.js(48)
  • CSS(46)
  • jQuery(34)
  • Nuxt.js(22)
  • Sass(21)
  • Gulp(17)
  • webpack(16)
  • PHP(14)
  • HTML(14)
  • GSAP(11)
  • React(10)
  • Vite(9)
  • Pug(9)
  • Laravel(8)
  • Cordova(8)
  • Linux(7)
  • Next.js(6)
  • Apache(5)
  • Shopify(4)
  • REST API(4)
  • Node.js(4)
  • Smarty(3)
  • Git(3)
  • JSON(3)
  • その他プログラミング(3)
  • Google Apps API(3)
  • Bootstrap(3)
  • 環境構築・システム(3)
  • TypeScript(2)
  • EJS(2)
  • RMS(2)
  • Docker(2)
  • Instagram API(2)
  • Excel VBA(2)
  • SVG(1)
  • Dart(1)
  • ECカートASP(1)
  • Markdown(1)
  • AMP(1)
  • Nginx(1)
  • XML(1)
  • shellscript(1)
  • Ruby(1)
  • MySQL(1)
  • 次の記事
  • 一覧に戻る
  • 前の記事

まだ依頼内容が決まっていなくても、ご相談ください。

課題の整理や小さな検証、UI/UX・フロントエンドの部分的な支援から始められます。

お問い合わせやご相談はこちらから

Webサイト・プロダクトの企画設計/UI・UXデザイン/フロントエンド開発合同会社デザインサプライ(DesignSupply. LLC)

Office:大阪府大阪市天王寺区清水谷町3-22
Email:info@designsupply-web.com
home
  • サイトトップ
about
  • 3つのポリシー
  • 経営理念
  • 代表・クリエイター紹介
  • 会社概要
services
  • ビジネス・プロダクト支援
  • UI/UX・フロントエンド開発支援
  • プロジェクトの進め方・支援体制
  • エデュケーション事業
works
  • 開発プロジェクト
  • クリエイティブ支援
  • 自社プロダクト
media
  • デザイン
  • プログラミング
  • マーケティング
  • Web開発
  • 写真・動画制作
contact
  • お問い合わせ
news
  • インフォメーション
privacy
  • プライバシーポリシー

WE THINK WITH YOU, DESIGN FOR PEOPLE, AND BUILD WHAT WORKS.

WE THINK WITH YOU, DESIGN FOR PEOPLE, AND BUILD WHAT WORKS.

© 2013 - 2026 DesignSupply.LLC
サイトトップへ
contact