Blogブログ

メモ化を使ったReactのパフォーマンスチューニングについて

こんにちは。エンジニアの砂町です。
今回はReactのパフォーマンスチューニングについてお話ししようと思います。
Reactでパフォーマンスチューニングを行う方法の一つとしてメモ化というものがあります。
メモ化を利用することで、コンポーネントのレンダリングを制御し、UXの向上に繋げることができます。言い換えると、無駄なレンダリングを無くして、動きを軽くするということです。

Reactでメモ化を行う方法として下記のようなものが挙げられます。

  • memo
  • useCallback
  • useMemo

では例を見ていきましょう。

memo

//親コンポーネント
export default function App() {
  const [value, setValue] = useState('')
  const onChange = (e)=>setValue(e.target.value)
  return (
    <div className="App">
      <input 
        type='text'
        value={value}
        onChange={onChange}
      />
      <Children />
    </div>
  );
}

//子コンポーネント
const Children = () => {
  let data = []
  for(let i=0; i<3; i++){
    console.log(i)
    data.push(<p key={i}>{i}</p>)
  }
  return data
}

このようなコードがあった時、親コンポーネントでinputの値が変更される度に、変更がない子コンポーネントでレンダリングも走ってしまいます。

下記のような形で、子コンポーネントをmemo化しておくことで、レンダリングは走らなくなります。

//子コンポーネント
const Children = React.memo(() => {
  let data = []
  for(let i=0; i<3; i++){
    console.log(i)
    data.push(<p key={i}>{i}</p>)
  }
  return data
})

useCallback

次にuseCallbackの例で関数のメモ化についてみていきます。
今度は先ほどのコードを少し修正して、親から子に関数を渡してみます。

//親コンポーネント
export default function App() {
  const [value, setValue] = useState('')
  const onChange = (e)=>setValue(e.target.value)
  const onClick = ()=>alert('ok')


  return (
    <div className="App">
      <input 
        type='text'
        value={value}
        onChange={onChange}
      />
      <Children onClick={onClick}/>
    </div>
  );
}

//子コンポーネント
const Children = React.memo(({onClick}) => {
  console.log('button')
  return(
    <button onClick={onClick}>OK</button>
  )
})

memo化しているにも関わらず、親コンポーネントでinputの値が変更される度に、子のコンポーネントではレンダリングが走ってしまっています。

これは親から子に渡しているonClick関数が、レンダリングの度に生成されているためです。
useCallbackでonClick関数のをマウント時のみ生成する形に変更すると、レンダリングは走らなくなります。useCallbackの第二引数では、ちなみにuseEffectと同じように依存関係を指定することができます。

  const onClick = React.useCallback(()=>alert('ok'), [])

useMemo

最後はuseMemoの例で変数のメモ化についてみていきます。
今回は一つのコンポーネントで動きの違いを確認します。

export default function App() {
  const [count, setCount] = useState(0)
  const onClick = ()=>setCount(count + 1)
  const result1 = count + 10
  const result2 = React.useMemo(()=>count + 10,[])
  const result3 = React.useMemo(()=>count + 10,[count])
  
  return (
    <div className="App">
      <button onClick={onClick}>+</button>
      <p>result1:10 + {count} = {result1}</p>
      <p>result2:10 + {count} = {result2}</p>
      <p>result3:10 + {count} = {result3}</p>
    </div>
  );
}


このようなコードがある時、result2は初回しか計算していないため、ずっと10となります。
result1とresult3は同じ動きになりますが、

  • result1ではレンダリングのたびに再計算される
  • result3ではcountの値が変更された場合にのみ再計算される

という違いがあります。

まとめ

今回はレンダリングの最適化について見てみました。
なんか動きが重たいってなった時はこの辺りを調整してみると軽くなるかもしれません。
ぜひ試してみてください!

砂町

執筆者

Developer

砂町