【React】レンダリングの最適化(memo,useCallback,useMemo)

2022/07/04に公開

レンダリング最適化

使い分け

memo useCallback useMemo
対象 コンポーネント 関数 変数

memo

コード(最適化前)

import {useState} = "react"
const Parent = () => {
  const [count, setCount] = useState(0);
  consol.log("親コンポーネントが呼ばれました");
  return(
    <div>
      <div>{count}</div>
      <button onClick={onClick}>Click</button>
      <ChildComponent />
    </div>
  );
}

const ChildComponent = () => {
  console.log("子コンポーネントが呼ばれました");
  return(
    <div>This is ChildComponent</div>
  );
}

出力結果

親コンポーネント中のボタン要素を押しても、子コンポーネントは変わっていないのに、親コンポーネントの再レンダリングに合わせて子コンポーネントも再レンダリングされてしまう。

//レンダリング時
親コンポーネントが呼ばれました
子コンポーネントが呼ばれました

//親ボタンclick時
親コンポーネントが呼ばれました
子コンポーネントが呼ばれました  //再レンダリング不必要

コード(最適化後)

import {useState, memo} = "react"
const Parent = () => {
  const [count, setCount] = useState(0);
  consol.log("親コンポーネントが呼ばれました");
  return(
    <div>
      <div>{count}</div>
      <button onClick={onClick}>Click</button>
      <ChildComponent />
    </div>
  );
}

const ChildComponent = memo(() => {
  console.log("子コンポーネントが呼ばれました");
  return(
    <div>This is ChildComponent</div>
  );
})

出力結果

親ボタンが押されたときに再レンダリングする必要のない子コンポーネントを再レンダリングしないようになった。

//レンダリング時
親コンポーネントが呼ばれました
子コンポーネントが呼ばれました

//親ボタンclick時
親コンポーネントが呼ばれました

memoの使うタイミング

  • 静的な子コンポーネント
  • propsが変数だけの子コンポーネント

useCallback

コード(最適化前)

import {useState, memo} = "react"
const Parent = () => {
  const [count, setCount] = useState(0);
  const [childCount, setChildCount] = useState(0);
  consol.log("親コンポーネントが呼ばれました");
  
  const onClick = () => {
    setCount(count + 1);
  }
  
  const onClildClick = () => {
    setChildCount(childCount + 1);
  }
  
  return(
    <div>
      <div>{count}</div>
      <button onClick={onClick}>Click</button>
      <ChildComponent onClick={onClildClick}/>
    </div>
  )
}

const ChildComponent = memo((props) => {
  console.log("子コンポーネントが呼ばれました")
  return(
    <div>
      <div>This is ChildComponent</div>
      <button onClick={props.onChildClick}>Click!<button>
    </div>
  )
})

出力結果

子コンポーネントにpropsで関数が渡されると"新しい関数を受け取った"ように振る舞って、親コンポーネントが再レンダリングされると、子コンポーネントに変化がなくても再レンダリングされてしまう。

//レンダリング時
親コンポーネントが呼ばれました
子コンポーネントが呼ばれました

//親ボタンclick時
親コンポーネントが呼ばれました
子コンポーネントが呼ばれました  //再レンダリング不必要

コード(最適化後)

import {useState, memo, useCallback} = "react"
const Parent = () => {
  const [count, setCount] = useState(0);
  const [childCount, setChildCount] = useState(0);
  consol.log("親コンポーネントが呼ばれました");
  
  const onClick = () => {
    setCount(count + 1);
  }
  
  const onClildClick = useCallback(() => {
    setChildCount(childCount + 1);
  },[childCount])
  
  return(
    <div>
      <div>{count}</div>
      <button onClick={onClick}>Click</button>
      <ChildComponent onClick={onClildClick}/>
    </div>
  )
}

const ChildComponent = memo((props) => {
  console.log("子コンポーネントが呼ばれました")
  return(
    <div>
      <div>This is ChildComponent</div>
      <button onClick={props.onChildClick}>Click!<button>
    </div>
  )
})

出力結果

//レンダリング時
親コンポーネントが呼ばれました
子コンポーネントが呼ばれました

//親ボタンclick時
親コンポーネントが呼ばれました

useCallbackの使うタイミング

  • propsに関数がある子コンポーネント

useMemo

コード(最適化前)

import {useState, memo, useCallback} = "react"
const Parent = () => {
  const [count, setCount] = useState(0);
  const [childCount, setChildCount] = useState(0);
  consol.log("親コンポーネントが呼ばれました");
  
  const onClick = () => {
    setCount(count + 1);
  }
  
  const squar = (count) => {
    console.log("2乗する関数が実行されました")
    return count * count
  }
  
  const squarCount = sqrar(count);
  
  const onClildClick = useCallback(() => {
    setChildCount(childCount + 1);
  },[childCount])
  
  return(
    <div>
      <div>{count}</div>
      <div>{squarCount}</div>
      <button onClick={onClick}>Click</button>
      <ChildComponent onClick={onClildClick}/>
      <div>{childCount}</div>
    </div>
  )
}

const ChildComponent = memo((props) => {
  console.log("子コンポーネントが呼ばれました")
  return(
    <div>
      <div>This is ChildComponent</div>
      <button onClick={props.onChildClick}>Click!<button>
    </div>
  )
})

出力結果

子コンポーネントのボタンを押したときに、親のステートに合わせて実行する計算処理が実行されてしまっている。

//レンダリング時
親コンポーネントが呼ばれました
2乗する関数が実行されました
子コンポーネントが呼ばれました

//親ボタンclick時
親コンポーネントが呼ばれました
2乗する関数が実行されました

//子ボタンclick時
親コンポーネントが呼ばれました
2乗する関数が実行されました  //再レンダリング不必要
子コンポーネントが呼ばれました

コード(最適化後)

import {useState, memo, useCallback, useMemo} = "react"
const Parent = () => {
  const [count, setCount] = useState(0);
  const [childCount, setChildCount] = useState(0);
  consol.log("親コンポーネントが呼ばれました");
  
  const onClick = () => {
    setCount(count + 1);
  }
  
  const squar = (count) => {
    console.log("2乗する関数が実行されました")
    return count * count
  }
  
  const squarCount = useMemo(sqrar(count),[count]);
  
  const onClildClick = useCallback(() => {
    setChildCount(childCount + 1);
  },[childCount])
  
  return(
    <div>
      <div>{count}</div>
      <div>{squarCount}</div>
      <button onClick={onClick}>Click</button>
      <ChildComponent onClick={onClildClick}/>
      <div>{childCount}</div>
    </div>
  )
}

const ChildComponent = memo((props) => {
  console.log("子コンポーネントが呼ばれました")
  return(
    <div>
      <div>This is ChildComponent</div>
      <button onClick={props.onChildClick}>Click!<button>
    </div>
  )
})

出力結果

//レンダリング時
親コンポーネントが呼ばれました
2乗する関数が実行されました
子コンポーネントが呼ばれました

//親ボタンclick時
親コンポーネントが呼ばれました
2乗する関数が実行されました

//子ボタンclick時
親コンポーネントが呼ばれました
子コンポーネントが呼ばれました

useMemoの使うタイミング

  • 数値処理を代入する変数があるとき

Discussion