- [React.js] 메모이제이션 완벽 가이드: memo, useCallback, useMemo와 Profiler 활용2025년 05월 25일 21시 09분 54초에 업로드 된 글입니다.작성자: DandyNow728x90반응형
React 메모이제이션 완벽 가이드: memo, useCallback, useMemo와 Profiler 활용
React 애플리케이션의 성능 최적화는 사용자 경험을 크게 좌우하는 중요한 요소이다. 특히 컴포넌트의 불필요한 리렌더링은 애플리케이션의 성능을 저하시키는 주요 원인 중 하나이다. 이 글에서는 React의 메모이제이션 기법들과 Profiler를 활용한 성능 분석 방법을 실제 예제를 통해 살펴본다.
React Profiler 소개
React Profiler는 컴포넌트 렌더링 성능을 측정하고 분석할 수 있는 도구이다. 개발자 도구의 Profiler 탭뿐만 아니라, 코드 내에서 직접 사용할 수 있는 Profiler 컴포넌트도 제공한다.
Profiler 컴포넌트 사용법
import { Profiler } from 'react'; function onRender(id, phase, actualDuration, baseDuration, startTime, commitTime, interactions) { console.log({ id, // Profiler tree의 "id" 속성 phase, // "mount" 또는 "update" actualDuration, // 실제 렌더링에 소요된 시간 baseDuration, // 최적화 없이 전체 서브트리를 렌더링하는 데 걸리는 시간 startTime, // React가 이번 업데이트 렌더링을 시작한 시점 commitTime, // React가 이번 업데이트를 커밋한 시점 interactions // 이번 업데이트에 해당하는 상호작용들의 집합 }); } <Profiler id="CommentItem" onRender={onRender}> <CommentItem /> </Profiler>Profiler의
onRender콜백 함수는 프로파일링되는 컴포넌트가 업데이트될 때마다 호출된다. 이를 통해 실시간으로 렌더링 성능을 모니터링할 수 있다.메모이제이션이 필요한 이유
React에서 상태가 변경되면 해당 컴포넌트와 그 하위 컴포넌트들이 모두 리렌더링된다. 이는 대부분의 경우 필요한 동작이지만, 때로는 불필요한 리렌더링으로 인해 성능 문제가 발생할 수 있다.
예제 코드를 살펴보면,
Memo컴포넌트에서 1초마다 새로운 댓글이 추가된다. 이때 기존의 댓글 항목들도 함께 리렌더링되는데, 실제로는 새로운 댓글만 렌더링되면 충분하다.React.memo: 컴포넌트 메모이제이션
React.memo는 고차 컴포넌트(Higher-Order Component)로, props가 변경되지 않은 경우 컴포넌트의 리렌더링을 방지한다.memo 사용 전후 비교
memo 사용 전:
function CommentItem({ title, content, likes }) { console.log(`Rendering: ${title}`); return ( <div> <span>제목: {title}</span> <p>내용: {content}</p> <span>Likes: {likes}</span> </div> ); }memo 사용 후:
import { memo } from 'react'; function CommentItem({ title, content, likes }) { console.log(`Rendering: ${title}`); return ( <div> <span>제목: {title}</span> <p>내용: {content}</p> <span>Likes: {likes}</span> </div> ); } export default memo(CommentItem);memo를 적용하면 props가 변경되지 않은CommentItem컴포넌트들은 리렌더링되지 않는다. 하지만 주의할 점이 있다.memo의 한계: 함수 props 문제
부모 컴포넌트가 리렌더링될 때마다 함수가 재생성되므로, 함수를 props로 전달하는 경우
memo의 효과가 사라진다.function Comments({ commentList }) { // 매번 새로운 함수가 생성됨 const handleClick = () => { alert("clicked"); }; return ( <div> {commentList.map((comment) => ( <CommentItem key={comment.title} handleClick={handleClick} // 항상 새로운 함수 참조 /> ))} </div> ); }useCallback: 함수 메모이제이션
useCallback은 함수를 메모이제이션하여 의존성 배열이 변경되지 않는 한 동일한 함수 참조를 반환한다.useCallback 사용법
import { useCallback } from 'react'; function Comments({ commentList }) { const handleClick = useCallback(() => { alert("clicked"); }, []); // 의존성 배열이 빈 배열이므로 함수가 한 번만 생성됨 return ( <div> {commentList.map((comment) => ( <CommentItem key={comment.title} handleClick={handleClick} // 항상 동일한 함수 참조 /> ))} </div> ); }useCallback의 효과
useCallback을 사용하면handleClick함수가 컴포넌트 리렌더링 시에도 동일한 참조를 유지한다. 따라서memo로 감싼CommentItem컴포넌트들은 props가 실제로 변경되지 않는 한 리렌더링되지 않는다.주의사항:
useCallback을 사용할 때는 의존성 배열을 정확히 명시해야 한다. 의존하는 값이 있다면 반드시 배열에 포함시켜야 한다.const handleClick = useCallback((id) => { console.log(`Clicked item ${id}`); // someStateValue를 사용하는 경우 doSomethingWith(someStateValue); }, [someStateValue]); // 의존성 배열에 포함useMemo: 값 메모이제이션
useMemo는 계산 비용이 높은 값을 메모이제이션하여 불필요한 재계산을 방지한다.useMemo 사용 전후 비교
useMemo 사용 전:
function CommentItem({ title, content, likes }) { // 컴포넌트가 리렌더링될 때마다 실행됨 const rate = (() => { console.log(`rate 계산 중: ${title}`); return likes > 10 ? "좋음" : "보통"; })(); return ( <div> <span>제목: {title}</span> <span>평가: {rate}</span> </div> ); }useMemo 사용 후:
import { useMemo } from 'react'; function CommentItem({ title, content, likes }) { const rate = useMemo(() => { console.log(`rate 계산 중: ${title}`); return likes > 10 ? "좋음" : "보통"; }, [likes, title]); // likes나 title이 변경될 때만 재계산 return ( <div> <span>제목: {title}</span> <span>평가: {rate}</span> </div> ); }useMemo의 효과
useMemo를 사용하면likes나title이 실제로 변경되지 않는 한rate값이 재계산되지 않는다. 이는 복잡한 계산이나 객체 생성 비용을 줄이는 데 효과적이다.실제 성능 개선 사례 분석
제공된 예제 코드를 통해 각 최적화 기법의 효과를 분석해보자.
1. 기본 상황 (최적화 없음)
// 모든 CommentItem이 매번 리렌더링됨 console.log(`actualDuration: comment1: 0.234ms`); console.log(`actualDuration: comment2: 0.198ms`); console.log(`actualDuration: comment3: 0.201ms`); console.log(`rate check comment1`); console.log(`rate check comment2`); console.log(`rate check comment3`);2. memo 적용 후
// 새로 추가된 댓글만 렌더링됨 console.log(`actualDuration: comment4: 0.156ms`); console.log(`rate check comment4`);3. useCallback 추가 후
// handleClick 함수 참조가 동일하므로 memo 효과 유지 // 기존 댓글들은 리렌더링되지 않음4. useMemo 추가 후
// rate 계산도 메모이제이션되어 불필요한 계산 방지 // 클릭 시에도 rate가 재계산되지 않음메모이제이션 사용 시 주의사항
1. 과도한 메모이제이션 피하기
메모이제이션에도 비용이 발생한다. 간단한 계산이나 자주 변경되는 값에 대해서는 메모이제이션이 오히려 성능을 저하시킬 수 있다.
// 좋지 않은 예: 간단한 계산을 메모이제이션 const simpleSum = useMemo(() => a + b, [a, b]); // 좋은 예: 복잡한 계산을 메모이제이션 const expensiveCalculation = useMemo(() => { return heavyComputationFunction(data); }, [data]);2. 의존성 배열 정확히 명시하기
의존성 배열을 잘못 명시하면 예상치 못한 버그가 발생할 수 있다.
// 잘못된 예: userId가 변경되어도 함수가 업데이트되지 않음 const fetchUser = useCallback(() => { return api.getUser(userId); }, []); // userId를 의존성에 포함하지 않음 // 올바른 예 const fetchUser = useCallback(() => { return api.getUser(userId); }, [userId]);3. 참조 동일성 이해하기
객체나 배열을 메모이제이션할 때는 참조 동일성을 고려해야 한다.
// 매번 새로운 객체가 생성됨 const config = { option1: true, option2: false }; // 올바른 방법 const config = useMemo(() => ({ option1: true, option2: false }), []);성능 측정과 최적화 전략
1. Profiler를 활용한 성능 측정
실제 성능 개선 효과를 확인하기 위해서는 측정이 필요하다. React Profiler를 활용하여 최적화 전후의 렌더링 시간을 비교할 수 있다.
function onRender(id, phase, actualDuration) { console.log(`${id} - ${phase}: ${actualDuration}ms`); } <Profiler id="CommentList" onRender={onRender}> <Comments commentList={comments} /> </Profiler>2. 병목 지점 식별
성능 문제가 발생하는 지점을 정확히 파악한 후 최적화를 적용해야 한다. 모든 컴포넌트에 메모이제이션을 적용하는 것보다는 실제로 성능 문제가 되는 부분에 집중하는 것이 효과적이다.
3. 단계적 최적화
- React.memo: 불필요한 컴포넌트 리렌더링 방지
- useCallback: 함수 props로 인한 memo 무효화 방지
- useMemo: 비용이 높은 계산 최적화
이 순서대로 단계적으로 적용하면서 성능 개선 효과를 측정하는 것이 좋다.
결론
React의 메모이제이션 기법들은 애플리케이션의 성능을 크게 개선할 수 있는 강력한 도구이다.
React.memo는 컴포넌트의 불필요한 리렌더링을 방지하고,useCallback은 함수 참조의 안정성을 보장하며,useMemo는 비용이 높은 계산을 최적화한다.하지만 이러한 기법들을 무분별하게 사용하는 것은 오히려 성능을 저하시킬 수 있다. Profiler를 활용하여 실제 성능을 측정하고, 병목 지점을 정확히 파악한 후 적절한 최적화 기법을 선택적으로 적용하는 것이 중요하다.
성능 최적화는 사용자 경험 개선의 핵심 요소이다. 올바른 메모이제이션 기법을 통해 더 빠르고 반응성 좋은 React 애플리케이션을 구축할 수 있다.
728x90반응형'언어·프레임워크 > React.js' 카테고리의 다른 글
[React.js] Spring Boot와 연동 시 `Invalid URL` 오류 해결기(리액트 프로젝트 중단점 설정) (0) 2025.06.18 [React.js] 네이버 지도 API PDF 변환 시 CORS 오류 완벽 해결법 (0) 2025.06.16 [React.js] `useEffect`와 `useLayoutEffect`의 차이: 깜박임 현상과 중간 값 노출 (0) 2025.05.23 [React.js] 네이버 지도 API 마커 중앙 정렬과 레이어 제어 (0) 2025.05.14 [React.js] Naver 지도 resize 이벤트 오류 해결하기 (`__event_relations__` 에러) (0) 2025.04.30 다음글이 없습니다.이전글이 없습니다.댓글