리액트를 쓰다 보면 "렌더링"이라는 말을 자주 접하게 된다. 하지만 리액트의 렌더링 과정은 단순히 return <div> 하는 걸로 끝나는 게 아니다. React의 내부 렌더링은 두 단계, Render Phase와 Commit Phase로 나뉘며, 이 둘을 명확히 이해해야만 예상치 못한 사이드 이펙트나 성능 문제를 피할 수 있다.
이번 포스팅에서는 이 두 단계가 각각 무엇을 하는지, 왜 나뉘어 있는지, 개발 시 주의할 점은 무엇인지까지 깔끔하게 정리해본다.
🎬 리액트 렌더링 프로세스의 큰 그림
리액트의 렌더링은 다음과 같이 이루어진다:
- Render Phase (렌더 단계): 어떤 UI를 보여줄지 계산하는 단계
- Commit Phase (커밋 단계): 계산된 결과를 DOM에 실제 반영하는 단계
이 둘을 분리한 이유는 성능 최적화, 병렬 처리(Concurrent Rendering), 예측 가능한 사이드 이펙트 실행 등을 가능하게 하기 위해서다.
🔁 1. Render Phase — 계산만 하는 순수한 단계
✅ 이 단계에서 하는 일
JSX를 분석해서 React Element Tree 생성
상태(state), props, context 등을 기반으로 새로운 UI를 계산
이 과정에서 DOM은 절대 변경되지 않음
🧠 중요한 특징
항목 | 설명 |
💡 DOM 변경 | ❌ 없음 |
⛔ 사이드 이펙트 | ❌ 금지 (useEffect나 DOM 조작은 여기서 실행되지 않음) |
🔁 실행 횟수 | 여러 번 발생 가능 |
🧵 중단 가능 | ✅ (Concurrent Mode에서는 중단하고 재시작 가능) |
📌 예제
function Counter({ count }) {
console.log("Rendering:", count); // Render Phase에서 실행됨
return <div>{count}</div>;
}
console.log는 Render Phase에서 실행되며, useEffect나 DOM 변경은 이 시점엔 일어나지 않는다.
✅ 2. Commit Phase — 실제로 DOM을 바꾸는 단계
Render Phase에서 계산된 내용을 실제 화면에 반영하는 단계다. 이때가 되어야만 사이드 이펙트가 실행된다.
✅ 이 단계에서 하는 일
- 실제 DOM 조작 (ReactDOM 내부에서 발생)
- ref 연결
- useEffect, useLayoutEffect 실행
- componentDidMount, componentDidUpdate, componentWillUnmount 호출 (클래스 컴포넌트의 경우)
🧠 중요한 특징
항목 | 설명 |
✅ DOM 변경 | 이 시점에 반영 |
🔥 사이드 이펙트 실행 | useEffect, useLayoutEffect |
🔁 실행 횟수 | 한 번만 발생 |
🚫 중단 불가 | 반드시 끝까지 수행됨 |
📌 예제
useEffect(() => {
console.log("Effect 실행됨!"); // Commit Phase에서 실행
}, []);
useEffect는 항상 Commit Phase에서만 실행되며, 그 전에는 아무 일도 하지 않는다.
🧪 예시 흐름: 상태 업데이트가 발생할 때
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("커밋 완료!"); // Commit Phase
}, [count]);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
클릭 시 실행 흐름
- setCount로 상태 업데이트
- Render Phase:
> 컴포넌트 함수 실행
> 새로운 JSX 계산
- Commit Phase:
> DOM 업데이트
> useEffect 실행 → "커밋 완료!" 로그 출력
🧠 왜 두 단계로 나누는 걸까?
1. 성능 최적화
렌더 단계는 비동기적으로 여러 번 수행 가능 → 빠르고 유연한 업데이트
DOM에 영향을 주지 않으므로 안전하게 "계산"만 가능
2. 예측 가능한 사이드 이펙트
useEffect는 항상 Commit 후 실행 → 비동기 로직과 충돌 최소화
3. 병렬/중단 처리 지원 (React 18 이후)
Render 중 우선순위 낮은 작업은 취소 가능 → 사용자 인터랙션 우선 처리
🧭 개발 시 주의할 점
체크리스트 | 이유 |
render()나 함수형 컴포넌트 안에서는 사이드 이펙트를 넣지 말 것 | 렌더는 여러 번 실행될 수 있음 |
useEffect, useLayoutEffect는 실제로 DOM이 업데이트된 후 실행됨 | 사이드 이펙트를 Commit Phase에서 안전하게 실행 |
StrictMode에서는 렌더링이 2번 호출됨 | 부작용을 감지하기 위한 의도적인 설계 |
리액트를 깊이 이해하고 싶다면 렌더와 커밋의 차이를 감각적으로 익히는 것이 매우 중요하다. 이 두 단계를 잘 이해하면 불필요한 렌더링을 피하고, 예측 가능한 코드 작성이 가능해진다. 특히 useEffect, ref, setState의 동작 시점을 오해하지 않도록 주의하자!
'개발 > Javascript & Typescript' 카테고리의 다른 글
[Javascript] 자바스크립트 호이스팅(Hoisting) 완전 정복 - 변수, 함수, let vs var의 차이까지! (64) | 2025.07.10 |
---|---|
[React] useEffect vs useLayoutEffect - 언제 어떤 걸 써야 할까? (100) | 2025.07.08 |
[React] React에서 key로 index를 쓰면 안 되는 이유 (60) | 2025.07.04 |
[React] Strict Mode 완전 정복 - 사이드이펙트, 더블 렌더링, Deprecated 감지까지 (63) | 2025.07.03 |
[Javascript] 함수가 기억을 한다고? 자바스크립트 클로저 쉽게 설명해봄 (58) | 2025.06.02 |