문제 상황
오늘은 코코 프로젝트를 개발하며 React 리렌더링을 최적화한 경험에 대해 작성해보려고 한다.
먼저 아래와 같이 렌더링을 확인하기 위한 dev tool을 사용했을 때, 대화 이력을 선택할 때마다 페이지의 모든 컴포넌트가 한꺼번에 리렌더링 되는 현상이 있었다.
특정 항목을 선택해도 전체 항목들이 함께 리렌더링 되는 것을 확인할 수 있었다.
리렌더링 조건
리렌더링 조건을 찾아보며 왜 이렇게 되는지 그 이유를 찾아 보았다. (이런 기본적인 내용이 생각나지 않았다니..)
그 이유는 페이지 컴포넌트에서 state를 관리하는데, 아마 그 state가 변하면서 하위 컴포넌트들이 모두 리렌더링 된 것이었다.
React에서의 리렌더링 조건은 아래와 같다.
- state가 바뀔 때
- props가 바뀔 때
- 부모 컴포넌트가 다시 그려질 때
- context API에서 값이 바뀔 때 (Provider 내부의 모든 컴포넌트가 리렌더링 된다.)
원인 파악
const SessionPage = () => {
const [selectedSessions, setSelectedSessions] = useState(new Set());
// 단일 선택
const handleSelectSession = (sessionId: number) => {
const newSelected = new Set(selectedSessions);
if (newSelected.has(sessionId)) {
newSelected.delete(sessionId);
} else {
newSelected.add(sessionId);
}
setSelectedSessions(newSelected);
};
// ...
return (
// ...
{allSessions.map((session, index) => {
const isLastSession = index === allSessions.length - 1;
return (
<div key={session.sessionId} ref={isLastSession ? lastSessionRef : null}>
<SessionItem
session={session}
isSelected={selectedSessions.has(session.sessionId)}
onSelect={handleSelectSession}
isSelectionMode={isSelectionMode}
/>
</div>
);
})}
// ...
}
각 세션 항목은 SessionItem 으로 데이터가 뿌려져 렌더링 되는데, 이때 onSelect 가 바로 페이지 컴포넌트의 state인 selectedSession를 변동시킨다.
따라서 전체 페이지 컴포넌트가 리렌더링 되는 것이다.
해결방법: 메모이제이션
React에서는 메모이제이션 방법으로 useCallback, memo, useMemo 가 있다.
useCallback은 함수 참조르 기억,
memo는 컴포넌트 참조를 기억 (자식 컴포넌트 재렌더링 방지),
useMemo는 값(객체)의 참조를 기억한다.
1. memo
컴포넌트를 memo로 감싸면 <부모가 리렌더링 되면 자식도 무조건 리렌더링된다>는 조건이 깨진다.
이제 자식의 props가 바뀔 때만 리렌더링 되도록 조건이 바뀐다.
const SessionItem = ({ session, isSelected, onSelect, isSelectionMode }: ISessionItemProps) => {
/ ... /
};
export default memo(SessionItem);
위와 같이 컴포넌트를 memo로 wrapping 해주었다.
이제 자식의 props (= SessionItem의 props)가 바뀌지 않는 한 리렌더링 되지 않을 것이다.
그러나 그럼에도 아직까지 모든 항목들이 리렌더링 되었다.
2. useCallback
함수는 렌더링마다 새로 생성되며, 참조가 바뀐다.
event handler 함수를 useCallback을 사용해 감싸주면 함수가 재생성 되는 것을 방지하여 참조가 유지되고, 자식의 props가 바뀌었다고 인식하지 않게 되므로, 리렌더링이 되는 것을 막는다고 한다.
따라서 props 중 onSelect가 계속 변하여 발생한 것으로 판단하였고, useCallback 처리 해주어 리렌더링을 최적화 해보았다.
// 단일 선택 (onSelect)
const handleSelectSession = useCallback((sessionId: number) => {
setSelectedSessions(prev => {
const newSelected = new Set(prev);
if (newSelected.has(sessionId)) {
newSelected.delete(sessionId);
} else {
newSelected.add(sessionId);
}
return newSelected;
});
}, []);
결과
이제 각 항목을 클릭할 때 모든 페이지가 리렌더링 되지 않게 되었다.
'웹 > React' 카테고리의 다른 글
| [React 딥다이브🌊] fiber 아키텍처와 동기 렌더링-커밋 프로세스 코드 뜯어보기 (4) | 2025.08.03 |
|---|---|
| [React] 좋아요 낙관적 업데이트 적용하기 (1) | 2025.07.21 |
| [코드 개선하기] try, catch vs React Query의 onSuccess, onError : 비동기 처리 로직 통일하기 (0) | 2025.06.25 |
| [React] 폰트 최적화 + preload 적용해 사용자 경험 개선하기 (0) | 2025.06.23 |
| [React, Vite, Vite-bundle-analyzer] CSR에서 초기 로딩 시간 단축하기 2 - 라이브러리 청크 단위로 정적 분리 (0) | 2025.06.03 |