최근 마이페이지에서 리스트 및 카드 컴포넌트의 리렌더링을 최적화하기 위해
React.memo
, useMemo
, useCallback
을 적극 활용하여 최적화를 진행했다.
하지만 적용 후 코드가 더 복잡해지고,
⚠️ 정말 필요한 곳에만 최적화를 적용한 것이 맞는지?
⚠️ 오히려 과한 최적화로 인해 성능이 저하될 수도 있는지?
라는 고민이 생겼다. 이에 대해 트러블슈팅을 진행하고, 어떤 기준으로 적용해야 하는지 정리해봤다.
앞서, 메모이제이션이란?
메모이제이션(memoization)은 이전에 계산한 값을 저장함으로써 동일한 계산을 반복하지 않도록 하는 기술입니다. 메모이제이션을 사용하면 이전에 계산한 값을 재사용하여 성능을 향상시킬 수 있습니다.
useCallback
남발 문제: 이벤트 핸들러 최적화가 정말 필요할까?useCallback
을 사용하면 이벤트 핸들러가 리렌더링될 때마다 새로 생성되지 않도록 최적화할 수 있다.useCallback
을 적용했다.tsx
복사편집
const handleClick = useCallback(() => {
console.log("버튼 클릭!");
}, []);
하지만, 이렇게 모든 핸들러에 useCallback
을 적용하는 것이 오히려 성능을 저하시킬 수 있음을 알게 됐다.
useCallback
을 남발하면 안 되는 이유useCallback
이 오히려 메모리를 추가적으로 사용하며, 함수를 캐싱하는 과정에서 불필요한 오버헤드가 발생할 수 있다.setState
변경 함수는 useCallback
이 없어도 무방하다.❌ 불필요한 useCallback
적용 예제
const handleToggle = useCallback(() => {
setIsOpen(prev => !prev);
}, []);
✔ 위 코드는 useCallback
없이도 성능 저하 없이 동작 가능
const handleToggle = () => {
setIsOpen(prev => !prev);
};
useCallback
을 언제 써야 할까?✔ 부모 컴포넌트가 자주 리렌더링되며, 자식 컴포넌트로 함수를 props
로 넘길 때만 useCallback
사용
✔ 이벤트 핸들러가 한 번만 실행되거나, 자식에게 전달되지 않는다면 useCallback
을 사용하지 않기
const handleClick = () => {
console.log("버튼 클릭!");
};
📌이렇게 하면 불필요한 메모리 사용을 줄이고 코드도 간결해진다.
useMemo
를 모든 데이터에 적용하는 문제: 정말 최적화가 되는가?useMemo
로 감싸서 불필요한 렌더링을 줄이고자 했다.useMemo
를 적용하는 것이 적절한지 확신이 없었다.const memoizedList = useMemo(() => guestbooksData.content, [guestbooksData]);
📌 문제점:
guestbooksData.content
자체가 이미 서버에서 받아온 값이므로 불변 객체이다.
즉, useMemo
를 사용하지 않아도 불필요한 재계산이 발생하지 않는다.
불필요한 useMemo
사용은 오히려 메모리 사용량을 증가시킬 수 있다.
useMemo
를 언제 써야 할까?✔ 리스트 데이터가 filter
, map
, sort
등 연산을 수행할 경우만 useMemo
를 사용
✔ 가공이 필요 없는 원본 데이터에는 useMemo
를 사용하지 않기
❌ 불필요한 useMemo
적용 예제
tsx
복사편집
const memoizedList = useMemo(() => guestbooksData.content, [guestbooksData]);
최적화된 코드
const guestbookList = guestbooksData.content; // 그대로 사용해도 무방
📌 불필요한 메모리 사용을 줄이고, 코드 가독성도 향상됨
React.memo
적용 범위 문제: 작은 컴포넌트에도 적용해야 할까?React.memo
를 적용하여 리스트 아이템(GatheringItem
, MainCard
)의 불필요한 리렌더링을 방지했다.React.memo
를 적용하는 것이 과연 적절한가?React.memo
를 적용하면, 컴포넌트의 props
가 변경되지 않으면 리렌더링을 방지할 수 있다.React.memo
를 적용해도 리렌더링이 계속 발생하므로 의미가 없다.❌ 불필요한 React.memo
적용 예제
const SmallComponent = React.memo(({ text }) => {
return <p>{text}</p>;
});
최적화된 코드
const SmallComponent = ({ text }) => {
return <p>{text}</p>;
};
📌 작은 컴포넌트에는 React.memo
를 적용하지 않는 것이 더 효율적! 그치만 대부분은 작은경우에도 많이 쓴다. 이는 브라우저 성능차이가 있음을 확인하고 느려졌으면 사용해보자~!
React.memo
, useMemo
, useCallback
은 언제 써야 할까?언제 사용해야 하나? | 언제 사용하지 말아야 하나? | |
---|---|---|
useCallback | 부모에서 자식으로 props 로 전달될 때 (이벤트 핸들러) | setState 만 변경하는 단순한 함수 |
useMemo | 비싼 연산(필터링, 정렬 등)을 캐싱할 때 | 단순한 값(숫자, 문자열)에는 필요 없음 |
React.memo | 부모가 자주 리렌더링되지만, 자식은 변경되지 않는 경우 | 작은 UI 컴포넌트 또는 props가 자주 변경되는 경우 |
✅ CSR 환경에서 React.memo
, useMemo
, useCallback
을 어떻게 활용해야 할까?
useMemo
를 통해 가공된 데이터 캐싱props
로 전달되는 함수만 useCallback
사용React.memo
적용 ✅ 단순한 setState
변경 함수에는 useCallback
사용하지 말 것
✅ 가공이 필요 없는 데이터에는 useMemo
사용하지 말 것
✅ 작은 UI 컴포넌트에는 React.memo
를 적용하지 말 것
✅ 렌더링 비용이 높은 경우(리스트, 차트, 복잡한 UI)에만 최적화 적용
🎯 즉, React.memo
, useMemo
, useCallback
은 남발하지 말고 "필요할 때만" 사용해야 최적화 효과를 제대로 볼 수 있다!