본문 바로가기
Develog/Front

[리액트 성능최적화] useCallback 과 useMemo의 차이와 React.memo

by 예 강 2024. 8. 17.

 

 

1. React 성능최적화에 사용되는 useCallback() 과 useMemo(), React.memo의 차이는 무엇일까?

useMemo()는 특정 계산의 결과를 메모이제이션 하며 의존성 배열이 변경되지 않는 한 동일한 결과를 반환하는 리액트 훅이다. 비용이 많이 드는 계산을 반복하지 않도록 방지하여 성능최적화에 쓰인다.

 

useCallback()은 '함수'의 메모이제이션에 사용된다. 컴포넌트가 리렌더링 될 때마다 동일한 함수가 재생성되는 것을 방지하며, 불필요한 리렌더링이나 성능 저하를 방지한다.

 

React.memo()는 자식컴포넌트가 부모컴포넌트에게서 전달받는 props가 변하지 않으면 자식컴포넌트가 리렌더링 되지 않게 해준다.

 

부모컴포넌트가 자식컴포넌트에 함수를 props로 전달해준다면 useCallback으로 함수를 감싸주면 props로 건네는 함수가 재생성되지 않고 참조를 유지한다. 즉 함수의 재생성을 하지 않는다.

 

useMemo()는 값 연산의 반복을 막는다. 특히 복잡한 계산을 할 때 유용하다.

useCallback()은 함수의 재생성을 막아 불필요한 리렌더링이나 성능저하를 방지한다.

React.memo()은 부모로부터 받는 props가 변하지 않을 경우 리렌더링 되지 않게 한다. useCallback과 함께 사용하면 성능을 극대화 할 수 있다.

2. React의 업데이트 규칙

1. 상태(state)의 변경

  •  컴포넌트의 state가 변경되면 해당 컴포넌트가 리렌더링 된다.

2. props의 변경

  • 컴포넌트에 전달되는 props가 변경되면 해당 컴포넌트가 리렌더링 된다.
  • 이는 함수나 객체의 참조가 변경되는 경우도 포함한다.

3. 부모컴포넌트가 리렌더링 될시 리렌더링 된다.

4. context의 변경

  • context값이 변경되면 해당 context를 구독하는 모든 하위 컴포넌트가 리렌더링 된다.

5. useEffect()의 의존성 배열이 변경되면 컴포넌트가 리렌더링 된다.

 

import React, { useState } from "react";

// 자식 컴포넌트가 부모로부터 props를 받지 않음
const ChildComponent = () => {
  console.log("ChildComponent 리렌더링");
  return <div>Child Component</div>;
};

const ParentComponent = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increase Count</button>
      <ChildComponent />
    </div>
  );
};

export default ParentComponent;

 

위 코드가 있을 때 버튼을 클릭하면 부모 컴포넌트가 리렌더링 되기 때문에 자식 컴포넌트도 리렌더링 된다.

import React, { useState } from "react";

// 자식 컴포넌트가 부모로부터 props를 받지 않음
const ChildComponent = React.memo(() => {
  console.log("ChildComponent 리렌더링");
  return <div>Child Component</div>;
});

const ParentComponent = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increase Count</button>
      <ChildComponent />
    </div>
  );
};

export default ParentComponent;

 

하지만 React.memo() 로 받는다면 props가 변경되지 않기 때문에 자식컴포넌트가 리렌더링 되지 않는다.

 

// 자식 컴포넌트
const ChildComponent = React.memo(({ onButtonClick }) => {
  console.log("ChildComponent 리렌더링");
  return <button onClick={onButtonClick}>Click me</button>;
});

const ParentComponent = () => {
  const [count, setCount] = useState(0);

  // 버튼 클릭 핸들러를 useCallback으로 메모이제이션
  const handleButtonClick = useCallback(() => {
    console.log("Button clicked");
  }, []);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increase Count</button>
      {/* ChildComponent에 handleButtonClick 함수를 전달 */}
      <ChildComponent onButtonClick={handleButtonClick} />
    </div>
  );
};

export default ParentComponent;

 

자식컴포넌트에 React.memo() 부모컴포넌트에 useCallback()을 해주면 부모가 리렌더링 될때 함수가 재생성 되지 않기 때문에 자식컴포넌트에 함수의 참조가 변경되지 않은 상태로 전달된다

=> 자식컴포넌트의 React.memo()는 props가 변경되지 않으면 리렌더링 하지 않는다.

 

=> 자식컴포넌트는 리렌더링 되지 않는다.

 

아무리 버튼을 눌러도 자식컴포넌트가 리렌더링 되었다는 문구는 나오지 않는다.

 

 

 

 

3. 언제나 성능최적화에 도움이 될까?

아니다. 

useCallback(), useMemo 와 같은 메모이제이션은 메모리를 차지하기 때문에 남용하면 자원이 낭비될 수 있다.

 

1. useMemo()는 복잡한 계산이나 연산이 반복적으로 수행되는데 결과가 잘 변하지 않는 데이터가 있을때 사용하는게 좋다. 계산결과가 잘 변경되거나 간단한 계산일 경우 사용하지 말자.

2. useCallback()과 React.memo()를 사용해 상태가 자주 변하지 않지만 그 값에 의존성이 큰 경우 컴포넌트에 사용하는게 

위 예시에 든 것 같은 간단한 버튼 클릭함수에는 메모이제이션을 사용하는건 좋지 않다.

 

 

메모이제이션은 적절하게 조절해서 사용하는게 좋다