useMemo와 useCallback

JINSUNG LEE·2022년 3월 6일
0
post-thumbnail



React를 활용하다 보면 간혹 불필요한 렌더링이 중복으로 동작 되는 경우를 발견할 수 있다.

아직까지 큰 프로젝트를 접한 경험은 없어 크게 문제로 여기지는 않았지만, 큰 퍼포먼스가 발생하는 웹 페이지를 다룰 경우 렌더링이 두세번 불필요하게 작동된다면, 브라우저 연산 과정에 속도 저하가 일어날 수 있다.

이러한 렌더링 제어를 할 수 있는 대표적 React Hook인 useMemouseCallback에 대해 알아보자.


불필요한 렌더링

  • 부모 컴포넌트 App.js

import { useCallback, useState } from 'react';
import './App.css';
import Button_Plus from './Button_Plus';
import Button_Minus from './Button_Minus';

function App() {

  const [countPlus, setCountPlus] = useState(0)
  const [countMinus, setCountMinus] = useState(0)

  const handlePlus = () => {
    console.log("플러스 클릭")
    return [countPlus, countPlus + 1, countPlus + 2]
  }

  const handleMinus = () => {
    console.log("마이너스 클릭")
    return [countMinus, countMinus - 1, countMinus - 2]
  }

  return (
    <div className="App">
      <button onClick={() => setCountPlus(countPlus + 1)}>+</button>
      <button onClick={() => setCountMinus(countMinus - 1)}>-</button>
      <Button_Plus handlePlus={handlePlus} />
      <Button_Minus handleMinus={handleMinus} />
    </div>


  );
}

export default App;
  • 자식 컴포넌트 Button_Plus.js

import { useEffect, useState } from "react"

function Button_Plus({handlePlus}) {
    const [plus, setPlus] = useState([])
    
    useEffect(() => {
        setPlus(handlePlus)
        console.log("플러스 자식 컴포넌트 렌더링")
    }, [handlePlus])
        


    return (
        <div style={{display: 'flex', justifyContent: 'center'}}>
            {plus.map((num, idx) => ( 
          		<div key={idx}>
            		{num}{plus.length - 1 !== idx ? ", " : null}
          		</div>))}
        </div>
    )
 
}

export default Button_Plus
  • 자식 컴포넌트 Button_Minus.js

import { useEffect, useState } from "react"

function Button_Minus({handleMinus}) {
    const [minus, setMinus] = useState([])
    
    useEffect(() => {
        setMinus(handleMinus)
        console.log("마이너스 자식 컴포넌트 렌더링")
    }, [handleMinus])
        


    return (
        <div style={{display: 'flex', justifyContent: 'center'}}>
            {minus.map((num, idx) => ( 
          		<div key={idx}>
            		{num}{minus.length - 1 !== idx ? ", " : null}
          		</div>))}
        </div>
    )
    
 
}

export default Button_Minus

위와 같은 코드를 구성하여 button을 하나라도 클릭할 경우 마이너스 자식 컴포넌트 렌더링, 플러스 자식 컴포넌트 렌더링가 아래와 같이 일어난다.

플러스 버튼을 누를 경우 setCountPlus로 state가 변경되어 Button_Plus컴포넌트의 props가 뿌려져야 할텐데, 다른 함수인 handleMinus 또한 영향을 받아, 렌더링이 모두 일어나는 현상을 확인할 수 있다.

먼저 리렌더링의 발생 조건은 아래처럼 구성되어 있다.

  • props가 변경될 때
  • state가 변경될 때
  • 부모 컴포넌트가 리렌더링되었을 때(자식 컴포넌트에게 리렌더 영향)

즉, 위 코드는 만일 +에 대한 button을 onClick할 경우 state가 변경되어 어느 하나의 state가 변경되면 전체 부모 컴포넌트가 다시 리렌더링이 발생하게 되어, handlePlus handleMinus가 새로 생성된다.

  1. 첫 렌더링이 일어나면서 handle함수와 count가 생성되어 렌더링 된다.
  2. 플러스 + 버튼을 클릭하면 handlePlus함수가 작동하여 state count가 변경된다.
  3. count의 변경으로 인해, 부모 컴포넌트는 리렌더링이 되게 되고, 변경된 props를
    자식 컴포넌트에게 내려주며, 자식 컴포넌트는 받은 Props를 전달한다.

결국 button을 누르면 두번의 렌더링이 발생하며, 그 이유는 컴포넌트가 렌더링될 때마다 해당 함수들을 새로 생성하여 결국 불필요한 렌더링이 일어난 것이다.

불필요한 렌더링을 방지하기 위해 React Hook인 useCallbackuseMemo가 필요하게 된다.




메모이제이션(Memoization)

언급한 React Hook을 사용하기에 앞서 Memoization에 대해 알고 넘어가야 하는 점이 있다.

Memoization이란 수행한 연산의 결과값을 별도의 공간에 저장하고 동일한 값이 들어오게 되면, 별도의 공간을 재활용하는 알고리즘 기법을 말한다.

그러나 많은 메모리가 쓰이게 될 수 있으므로 메모리 관리에 있어 주의하여야 한다.




useMemo

메모이제이션된 을 반환한다.
useMemo(() => 실행문, [종속배열])

종속배열에 변화가 일어날 경우, 실행문이 작동된다.

다시 말해 종속 배열에 의존하여 useMemo가 실행된다고 보면 좋다.


const handlePlus = useMemo(() => {
    console.log("플러스 클릭")
    return [countPlus, countPlus + 1, countPlus + 2]
  }, [countPlus])

  const handleMinus = useMemo(() => {
    console.log("마이너스 클릭")
    return [countMinus, countMinus - 1, countMinus - 2]
  }, [countMinus])

종속배열은 각각 countPlus, countMinus으로 묶여 있으므로 각 상태값이 변화가 일어날 때에 useMemo의 실행문이 작동되며, 위와 같은 방식을 활용하면 불필요한 렌더링을 방지할 수 있게 된다.

또한, 을 반환한다고 언급하였는데, Button_Plus 컴포넌트의 props를 콘솔로그 찍어보면 아래와 같이 이 출력된다.




useCallback

메모이제이션된 함수를 반환한다.
useCallback(() => 실행문, [종속배열])


  const handlePlus = useCallback(() => {
    console.log("플러스 클릭")
    return [countPlus, countPlus + 1, countPlus + 2]
  }, [countPlus])

  const handleMinus = useCallback(() => {
    console.log("마이너스 클릭")
    return [countMinus, countMinus - 1, countMinus - 2]
  }, [countMinus])

코드 작성 방법은 useMemo와 큰 차이가 없다.

그러나 해당 함수를 props에 넘겨 콘솔로그를 찍어보면 아래와 같이 함수가 출력된다.




차이점

useMemo와 useCallback을 통해 변화하지 않은 요소에 불필요 렌더링을 하지 않고, 메모이제이션 방법을 활용하여 이전의 Virtual DOM을 재사용 한다.

그렇다면 둘의 차이점은 무엇일까?

useMemo는 해당 함수의 연산량이 많을때 이전 출력 결과값을 메모이제이션 방법을 통해 재사용이 필요할 경우, useCallback은 함수가 재생성 되는것을 방지하기 위한 경우로 나뉜다.

profile
https://californialuv.github.io/Tech_Blog 이사 갔어용 🌎 🚀

0개의 댓글