React에서 렌더링(Rendering)은 컴포넌트 함수를 호출하여 현재 State와 Props를 기반으로 화면에 그려야 할 UI를 계산하는 과정입니다. 렌더링이 곧 화면 갱신을 의미하지는 않습니다. 계산 결과가 이전과 다를 때만 실제 DOM이 변경됩니다.
초기 렌더링(Initial Render)은 앱이 처음 시작될 때 발생합니다. React는 루트 컴포넌트부터 시작하여 전체 컴포넌트 트리를 순서대로 실행하고, 그 결과를 실제 DOM에 반영합니다.
// index.js
import { createRoot } from 'react-dom/client';
import App from './App';
const root = createRoot(document.getElementById('root'));
root.render(<App />); // 초기 렌더링 시작
리렌더링(Re-render)은 이미 마운트된 컴포넌트가 다시 렌더링되는 것을 말합니다. 리렌더링이 발생하면 React는 이전 렌더링 결과와 새 결과를 비교하여 실제로 변경된 DOM만 업데이트합니다.
리렌더링은 다음 세 가지 경우에 발생합니다.
setState 함수가 새로운 값으로 호출될 때State 변경 → 해당 컴포넌트 리렌더링 → 자식 컴포넌트 리렌더링
부모의 리렌더링이 자식까지 전파되는 것은 기본 동작입니다. 자식 컴포넌트를 React.memo로 감싸면 props가 변경되지 않을 때 리렌더링을 건너뛸 수 있습니다.
React의 렌더링은 두 단계로 나뉩니다.
┌─────────────────────────────────────────────┐
│ Render Phase │
│ 컴포넌트 함수 실행 → React 엘리먼트 트리 생성 │
│ 이전 트리와 새 트리를 비교 (Diffing) │
└─────────────────────┬───────────────────────┘
│ 변경된 부분만 전달
┌─────────────────────▼───────────────────────┐
│ Commit Phase │
│ 변경 사항을 실제 DOM에 반영 │
│ useEffect, useLayoutEffect 실행 │
└─────────────────────────────────────────────┘
Render Phase는 순수한 계산 단계입니다. 컴포넌트 함수를 실행하여 React 엘리먼트 트리를 만들고, 이전 트리와 비교(Diffing)하여 변경 사항을 파악합니다. 이 단계는 부작용(side effect)이 없어야 합니다.
Side Effect: 렌더링 외부에 영향을 주는 작업. DOM 직접 조작, 네트워크 요청, 타이머 등이 해당됩니다.
Commit Phase는 Render Phase에서 계산된 변경 사항을 실제 DOM에 반영하는 단계입니다. DOM 업데이트 후 useLayoutEffect, 그다음에 useEffect가 실행됩니다.
렌더링 자체(컴포넌트 함수 실행)는 가볍지만, 불필요한 리렌더링이 너무 많으면 성능 문제가 생길 수 있습니다. React가 제공하는 최적화 도구는 다음과 같습니다.
React.memo: props가 바뀌지 않으면 컴포넌트 리렌더링을 건너뜁니다.useMemo: 계산 비용이 큰 값을 메모이제이션합니다.useCallback: 함수를 메모이제이션하여 자식에게 안정적인 참조를 전달합니다.단, 최적화는 실제 성능 문제가 확인된 경우에만 적용하는 것이 좋습니다. 섣불리 적용하면 코드 복잡도만 높아질 수 있습니다.
React 렌더링은 컴포넌트 함수를 실행하여 UI를 계산하는 Render Phase와, 그 결과를 DOM에 적용하는 Commit Phase로 나뉩니다. State·Props 변경 또는 부모 리렌더링이 트리거가 되며, 실제 DOM은 변경된 부분만 최소한으로 갱신됩니다.