[React] 리액트 렌더링 최적화 (useCallback, useMemo, React.memo)

강수영·2025년 6월 15일
0

📌 리액트는 언제 리렌더링될까?

  • state가 바뀌었을 때
  • props가 바뀌었을 때

✨ 예시를 통해서 알아보자! (useCallback + React.memo)

// Parent.tsx

function App() {
  const [state, setState] = useState(0);

  const onClick = () => {};

  useEffect(() => {
    setTimeout(() => {
      setState(1);
    }, 1000);
  }, []);

  return (
    <div className="App">
      <Child onClick={onClick} />
    </div>
  );
}
// Child.tsx

function Child({ onClick }) {
  return (
    <>
      {Array.from({ length: 100 }, (_, idx) => (
        <div key={idx} onClick={onClick}>
          Hello, world!
        </div>
      ))}
    </>
  );
}

App 컴포넌트만 리렌더링됐는데 왜 Child도 리렌더링될까?

App 컴포넌트가 리렌더링되면, onClick 함수도 다시 정의되면서 새로운 참조값을 갖게 됩니다.

React는 Child 컴포넌트에 전달된 onClick prop이 이전과 다른 값이라고 판단하고, 결과적으로 Child리렌더링하게 됩니다.

❓ 함수의 참조값이 바뀌지 않도록 해주면, Child 의 불필요한 렌더링을 방지해줄 수 있지 않을까?

이런 불필요한 리렌더링을 막기 위해서는 함수의 참조값을 고정해줄 필요가 있고, 이때 사용하는 것이 바로 useCallback Hook입니다.

useCallback은 함수를 메모이제이션해주어, 의존성이 바뀌지 않는 한 같은 함수 참조값을 재사용하게 해줍니다.

  const onClick = useCallback(() => { }, []);

하지만 위와 같이 함수의 참조값을 useCallback으로 고정해주었음에도, 여전히 Child 컴포넌트가 리렌더링되는 것을 확인할 수 있습니다.

❓ useCallback을 사용해 Child 컴포넌트에 전달되는 props가 이전과 동일한데, 왜 리렌더링이 발생할까?

React는 기본적으로 모든 부모 컴포넌트가 리렌더링되면, 자식 컴포넌트도 함께 리렌더링합니다.

즉, props가 같더라도, React는 자식 컴포넌트를 자동으로 리렌더링에서 제외하지 않습니다.

❓ 그렇다면 Child 컴포넌트의 리렌더링을 막으려면 어떻게 해야 할까?

이럴 때 사용할 수 있는 방법이 바로 React.memo입니다.

React.memo는 전달받은 props가 이전과 동일한 경우, 컴포넌트의 리렌더링을 생략하고 이전에 렌더링된 결과를 재사용하는 고차 컴포넌트(HOC)입니다.

// Child 컴포넌트 React.memo 적용

function Child({ onClick }) {
  return (
    <>
      {Array.from({ length: 100 }, (_, idx) => (
        <div key={idx} onClick={onClick}>
          Hello, world!
        </div>
      ))}
    </>
  );
}

export default React.memo(Child)

Child 컴포넌트가 렌더링되기 전에, React.memo는 전달받은 onClick prop의 이전 값과 현재 값을 비교합니다.
두 값이 같다면, Child렌더링을 생략하고 이전 결과를 재사용하게 됩니다.


✨ 예시를 통해서 알아보자! (useMemo + React.memo)

// Parent.tsx

function App() {
  const [state, setState] = useState(0);

  const item = {
	  name : '강수영',
	  city : '서울'
  }

  useEffect(() => {
    setTimeout(() => {
      setState(1);
    }, 1000);
  }, []);

  return (
    <div className="App">
      <Child item={item} />
    </div>
  );
}
// Child.tsx

function Child({ item }) {
  return (
    <>
      {Array.from({ length: 100 }, (_, idx) => (
        <div key={idx}>
          이름: {item.name} <br />
          지역: {item.city}
        </div>
      ))}
    </>
  );
}
export default React.memo(Child)

❓ 객체를 props로 전달하면 어떤 일이 일어날까?

Child 컴포넌트에 React.memo를 적용하면, 일반적으로 props가 변하지 않는 한 리렌더링을 방지할 수 있습니다.

하지만 Child가 여전히 리렌더링되고 있습니다. 그 이유는 Child 컴포넌트 입장에서는 매번 새로운 item 객체를 전달받고 있기 때문입니다.

App 컴포넌트가 리렌더링될 때마다, item 객체는 새로 생성되며, 이는 참조형 데이터이기 때문에 항상 다른 참조값을 가지게 됩니다. React는 이 다른 참조값을 "변경된 props"로 판단하고, 결국 Child를 리렌더링하게 됩니다.

이러한 상황에서는 useMemo Hook을 사용해 객체를 메모이제이션해주는 것이 좋습니다. useMemo는 특정 값이 의존성에 따라 재계산될 필요가 있을 때만 새 값을 생성하며, 불필요한 객체 생성과 리렌더링을 방지할 수 있습니다.

const memoizedItem = useMemo(() => item, []);

참초

profile
프론트엔드 개발자

0개의 댓글