LoginSignup
0
0

More than 3 years have passed since last update.

React.memoで無駄な再レンダリングを行わないようにする

Last updated at Posted at 2020-11-18

Reactの木構造とrender

  • stateが変わると再レンダリングされる
  • 親のstateが変わると全ての子が再レンダリングされる

React.memoの活用

  • 親のstateが変わっても子に渡すpropsの値に変化がなければ再レンダリングしない

React.memoの基本構文

  • コンポーネントごとに設定する
import React, {FC} from 'react';

interface Props {
  trigger: number
}

// React.memo()でラップする
const MemoChild: FC<Props> = React.memo(({trigger}) => {
  // なんらかの重い処理
  return (
    <div>Memo Component: {trigger}</div>
  )
})

export default MemoChild;

サンプルコードで動作確認

App.tsx
import React, {FC, useState} from 'react';
import {SfcChild, MemoChild, DeepEqualMemoChild} from "./components";

export interface Obj {
  deepTrigger: number
}

const App: FC = () => {
  const [sfcTrigger, setSfcTrigger] = useState<number>(0)
  const [memoTrigger, setMemoTrigger] = useState<number>(0)

  const countUpSfc = () => {
    setSfcTrigger(prevState => prevState + 1)
  }

  const countUpMemo = () => {
    setMemoTrigger(prevState => prevState + 1)
  }

  return (
      <div>
        <h2>React Memo Test</h2>
        <button onClick={countUpSfc}>SFC</button>
        <button onClick={countUpMemo}>Memo</button>

        <SfcChild trigger={sfcTrigger} />
        <MemoChild trigger={memoTrigger} />
      </div>
  );
};

export default App;
src/components/StatelessFunctionalComponent.tsx
import React, {FC} from 'react';

interface Props {
    trigger: number
}

const StatelessFunctionalComponent: FC<Props> = ({trigger}) => {
    const startTime = performance.now()

    for (let i=0; i < 10000; i++) {
        // 16桁の文字列を乱数生成
        const S="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        const N=16
        Array.from(Array(N)).map(()=>S[Math.floor(Math.random()*S.length)]).join('')
    }

    const endTime = performance.now()

    console.log(`SFC: ${endTime - startTime} milliseconds`)

    return (
        <div>Stateless Functional Component: {trigger}</div>
    );
};

export default StatelessFunctionalComponent;
src/components/ReactMemoComponent.tsx
import React, {FC} from 'react';

interface Props {
    trigger: number
}

const ReactMemoComponent: FC<Props> = React.memo(({trigger}) => {
    const startTime = performance.now();

    for (let i=0; i < 10000; i++) {
        // 16桁の文字列を乱数生成
        const S="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        const N=16
        Array.from(Array(N)).map(()=>S[Math.floor(Math.random()*S.length)]).join('')
    }

    const endTime = performance.now()

    console.log(`Memo: ${endTime - startTime} milliseconds`)

    return (
        <div>React Memo Component: {trigger}</div>
    )
})

export default ReactMemoComponent;

コンソールログを確認しながら、各ボタンを押して動作を確認する。
SFCボタンを押す
StatelessFunctionalComponentのログだけ流れてくる
StatelessFunctionalComponentだけ再描画されている(ReactMemoComponentは再描画されていない)

Memoボタンを押す
StatelessFunctionalComponentReactMemoComponentのログが流れてくる
StatelessFunctionalComponentReactMemoComponentの両方が再描画されている

Deep Equalの活用

  • 比較関数を書く
  • trueを返すとレンダリングしない
  • falseを返すとレンダリングする

サンプルコードで動作確認

App.tsx
import React, {FC, useState} from 'react';
import {SfcChild, MemoChild, DeepEqualMemoChild} from "./components";

export interface Obj {
  deepTrigger: number
}

const App: FC = () => {
  const [sfcTrigger, setSfcTrigger] = useState<number>(0)
  const [memoTrigger, setMemoTrigger] = useState<number>(0)
  const [obj, setObject] = useState<Obj>({deepTrigger: 0})

  const countUpSfc = () => {
    setSfcTrigger(prevState => prevState + 1)
  }

  const countUpMemo = () => {
    setMemoTrigger(prevState => prevState + 1)
  }

  const countUpDeepTrigger = () => {
    setObject(prevState => {
      return {deepTrigger: prevState.deepTrigger + 1}
    })
  }

  return (
      <div>
        <h2>React Memo Test</h2>
        <button onClick={countUpSfc}>SFC</button>
        <button onClick={countUpMemo}>Memo</button>
        <button onClick={countUpDeepTrigger}>Deep Equal Memo</button>

        <SfcChild trigger={sfcTrigger} />
        <MemoChild trigger={memoTrigger} />
        <DeepEqualMemoChild trigger={memoTrigger} obj={obj}/>
      </div>
  );
};

export default App;
src/components/DeepEqualMemoComponent.tsx
import React, {FC} from 'react';
import {Obj} from "../App";

interface Props {
    trigger: number
    obj: Obj
}

const DeepEqualMemoComponent: FC<Props> = React.memo(({trigger, obj}) => {
    const startTime = performance.now()

    for (let i=0; i < 10000; i++) {
        // 16桁の文字列を乱数生成
        const S="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        const N=16
        Array.from(Array(N)).map(()=>S[Math.floor(Math.random()*S.length)]).join('')
    }

    const endTime = performance.now()

    console.log(`Deep Equal Memo: ${endTime - startTime} milliseconds`)

    return (
        <div>Deep Equal Memo Component: {trigger}</div>
    )
}, (prevProps: Props, nextProps: Props) => {
    const prevDeepTrigger = prevProps.obj.deepTrigger
    const nextDeepTrigger = nextProps.obj.deepTrigger
    return (prevDeepTrigger === nextDeepTrigger)
})

export default DeepEqualMemoComponent;

SFCボタンを押す
StatelessFunctionalComponentのログだけ流れてくる
StatelessFunctionalComponentだけ再描画されている
ReactMemoComponentDeepEqualMemoComponentは再描画されていない

Memoボタンを押す
StatelessFunctionalComponentReactMemoComponentのログが流れてくる
StatelessFunctionalComponentReactMemoComponentが再描画されている
DeepEqualMemoComponentは再描画されていない
memoTriggerも受け取っているが、objの変更を検知しない限り再レンダリングは行わない

Deep Equal Memoボタンを押す
StatelessFunctionalComponentDeepEqualMemoComponentのログが流れてくる
StatelessFunctionalComponentDeepEqualMemoComponentが再描画されている
ReactMemoComponentは再描画されていない

0
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
0
0