
리액트를 사용하다 보면, 종종 "생각보다 너무 자주 리렌더링이 발생한다"는 문제를 마주하게 된다. 특히, 부모 컴포넌트가 자주 렌더링되면 그 자식 컴포넌트들도 불필요하게 리렌더링되는 경우가 많다. 이런 경우 성능 최적화가 필요한데, 그 대표적인 방법들을 하나씩 고민해보며 정리해보자!!
React.memo는 함수형 컴포넌트를 메모제이션하여, props가 변경되지 않으면 리렌더링을 방지하는 고차 컴포넌트(HOC)이다.
const MemoizedComponent = React.memo(MyComponent);
예를 들어, 부모 컴포넌트에서 매번 새로 렌더링될 때 자식 컴포넌트도 같이 렌더링된다면, React.memo를 사용해 props가 바뀌지 않았을 경우 리렌더링을 막을 수 있다.
하지만, 주의할 점은 React.memo는 props의 얕은 비교만 수행한다. 즉, 객체나 배열 같은 참조 타입이 매번 새로 생성되는 경우, props가 변경되는 것으로 인식하로 리렌더링된다.
그래서 이런 경우에는 useMemo, useCallback을 조합해서 써야 진짜 효과를 볼 수 있다 !
useMemo는 계산 비용이 높은 값을 메모제이션하는데 사용한다.
const memoizedValue = useMemo(() => computeExpensiveValue(a,b), [a,b]);
예를 들어 리스트를 정렬하거나 필터링하는 연산처럼 리렌더링마다 계속 반복하기에는 비싼 작업이 있다면, useMemo를 사용해서 의존성 값이 바뀔 때만 다시 계산되도록 할 수 있다.
useCallback은 함수를 메모제이션하는데 사용된다. 주로 자식 컴포넌트에 콜백을 props로 넘길 때 유용하다.
const memoizedCallback = useCallback(() => doSomething(a,b), [a,b]);
이거 안 쓰면 함수는 매번 새로 정의되기 때문에, 참조가 변경되어 자식 컴포넌트가 불필요하게 리렌더링된느 문제가 생긴다.
정답은 절대 ! 아니다 !
메모제이션은 "최적화"이지 "기본값"이 아니다.
이유를 말하자면
useMemo, useCallback도 결국 비용이 든다.예를 들어 아래 코드처럼 엄청 단순한 함수는 굳이 useCallback을 쓸 필요가 없다.
const handleClick = () => {
console.log("clicked");
}
근데 이런 함수에 쓰는건 미친거....
이걸 useCallback(() => {}, [])로 바꿔도, 실제로 성능이 더 좋아지지 않고, 오히려 복잡성을 추가할 뿐이다.
반대로, 렌더링마다 반복되는 무거운 연산이라면 useMemo가 유리하다.
말로만 "리렌더링이 많다"고 하지 말고, 실제로 확인하는게 가장 확실하다.
React DevTools의 Profiler 탭을 이용하면,
이런걸 시각적으로 확인할 수 있다.
이를 통해 진짜 병목이 발생하는 부분을 찾아내고, 선별적으로 최적화하는 것이 가장 이상적인 접근이다.