[React] React.memo vs useCallback vs useMemo 비교

Jinny·2023년 11월 10일
0

React

목록 보기
5/23

1. 리렌더링 발생 조건

1) 컴포넌트에서 state가 변경되었을 때
2) 컴포넌트가 내려받은 props가 변경되었을 때
3) 부모 컴포넌트가 리렌더링되면 자식 컴포넌트도 모두!

최적화(Optimiztion)
리액트에서 렌더링이 자주 일어나는 것은 좋지 않으며
이를 줄이는 작업이 최적화라고 한다.

💡최적화의 대표적인 방법

  • memo(React. memo) : 컴포넌트를 캐싱
  • useCallback : 함수를 캐싱
  • useMemo : 값을 캐싱

2. memo(React.memo)

부모 컴포넌트의 state 변경으로 인해 props가 변경되지 않으면 memo를 통해 자식 컴포넌트의 리렌더링을 방지할 수 있다.

부모 컴포넌트가 App, 자식 컴포넌트가 Box1,Box2라 하자.

export default function App() {
  console.log('App 컴포넌트 렌더링');
  const [count,setCount] = 0;
  const handleAdd = () => {
     setCount(count+1);
  })
  
  return (
    <>
    	<p>현재 카운트 {count}</p>
    	<button onClick = {handleAdd}>Add</button>
    	<div>
      		<Box1/>
      		<Box2/>
     	<div> 
    </>
  );
}

➡️ count 값이 변경될 때마다 Box1,2 컴포넌트 모두
리렌더링이 발생한다.

React.memo를 이용하면 컴포넌트를 메모리에 저장할 수 있다.

function Box1() {
  console.log('Box1 렌더링')
  return <div>Box1</div>;
}
export default React.memo(Box1);

➡️ 최초 렌더링 외에 부모 컴포넌트에서 상태가 변해도 자식 컴포넌트인 Box1,Box2 컴포넌트는 리렌더링이 일어나지 않는다.

const Box1 = memo(() => {
   console.log('Box1 렌더링')
   return <div>Box1</div>;
}
export default Box1;

함수 선언 시 memo를 쓸 경우, 이와 같이 작성할 수 있다.

3. useCallback

인자로 들어오는 함수 자체를 기억할 때 사용된다.

만약에 Box1 컴포넌트에서 값을 초기화하는 함수를 props를 통해 받는다고 하자.

export default function App() {
  console.log('App 컴포넌트 렌더링');
  const [count,setCount] = 0;
  const handleAdd = () => {
     setCount(count+1);
  })
  const resetCount = () => {
     setCount(0);
  })
  
  return (
    <>
    	<p>현재 카운트 {count}</p>
    	<button onClick = {handleAdd}>Add</button>
    	<div>
      		<Box1 resetCount={resetCount}/>
      		<Box2/>
     	<div> 
    </>
  );
}
------------------------------------

function Box1({resetCount}) {
  console.log('Box1 렌더링')
  return <div>
    Box1
  <button onClick={resetCount}>리셋</button>
  </div>;
}
export default React.memo(Box1);
  • Box1 컴포넌트에서 리셋 버튼을 클릭하면 Box1 컴포넌트도 리렌더링이 발생한다.
  • App 컴포넌트가 리렌더링 될 때 리셋하는 함수도 다시 생성되기 때문에 자식 Box1 컴포넌트도 새로운 props를 전달받게 되어 리렌더링이 일어나는 것이다.
  • 함수도 객체의 종류로 주소값이 저장되기 때문에 새롭게 생성되면 새로운 주소값을 갖게된다.

➡️ 따라서 리셋 함수를 별도로 메모리에 저장하면 된다!

useCallback도 useEffect와 같이 의존성 배열을 넣는다.

const resetCount = useCallback(() => {
     setCount(0);
  },[])
  • App.jsx에서 처음 렌더링될 때 리셋함수를 메모리 공간에 저장한다. 따라서 리렌더링이 일어나도 해당 함수는 새롭게 업데이트되지 않는다.
  • 박스 1 props에 있는 함수는 App.jsx에서 첫 렌더링될 때의 주소값을 계속 가지고 있기 때문에 리렌더링이 일어나지 않는다.

🧐 리셋 버튼을 누르는 시점에서 count를 출력하면 값이
어떻게 나올까?

일단 리셋하는 함수에서 출력해보자.

const resetCount = useCallback(() => {
     console.log(`${count}에서 0으로 변함`);
     setCount(0);
  },[])

놀랍게도 '0에서 0으로 변함'이 출력된다.
useCallback을 통해 카운트를 설정한 값(setCount(0))이 스냅샷으로 저장되어 있기 때문에 0으로 나온다.

리셋을 누른 시점에서의 count를 출력하고 싶으면
의존성 배열에 count 변수를 추가하면 된다.

const resetCount = useCallback(() => {
     console.log(`${count}에서 0으로 변함`);
     setCount(0);
  },[count])

카운트 값이 변경될 때마다 useCallback이 다시 저장되기 때문에 해당 시점에서의 count를 제대로 출력하지만
Box1 컴포넌트도 리렌더링이 일어난다.

4. useMemo

함수가 리턴하는 값 자체를 기억할 때 사용된다.
의존성 배열 역시 존재한다. 주로 무거운 일을 할 때 사용된다.

export default Counter() {
   const [count,setCount] = useState(0);
   const handleAdd = () => {
      setCount(count+1);
   }
   
   const calculate = () => {
      for(let i =0; i < 1000000;i++){
         console.log('☺️');
      }
      return 100;
   }
   const value = calculate();
   
   return (
      <>
      	<p>{count}</p>
      	<button onClick = {handleAdd}>+</button>
     </>
   );
}
  • 카운터 컴포넌트가 렌더링될 때 마다 calculate 함수가 호출되기 때문에 렌더링되는 시간이 오래 걸린다.
  • 이 함수는 결국 100을 반환하기 때문에 매번 호출하는 것이 비효율적이다.
  • 또한 count 상태 값이 변경될 때도 리렌더링이 발생하기 때문에 count가 느리게 증가되는 것을 볼 수 있다.

➡️ 계산 작업으로 반환한 값을 useMemo를 통해 메모리에 저장한다.

const value = useMomo(() => calculate(),[]);

👉아까와 다르게 바로바로 카운트가 증가되는 것을 볼 수 있다.

0개의 댓글