[React] React 렌더링 과정 정리
React의 “렌더링”은 화면에 무엇을 그릴지 결정하고, 실제 DOM(브라우저 화면)을 최소한으로 갱신하는 일련의 과정이다.
흔히 “Virtual DOM을 쓴다”라고 말하지만, 핵심은 상태 변화 → UI 계산 → 변경점 비교 → 필요한 부분만 반영이라는 흐름에 있다.
본 글에서는 React가 컴포넌트를 어떻게 평가하고, 어떤 순서로 화면이 바뀌는지 렌더링 과정을 중심으로 정리한다.
React 렌더링이 시작되는 순간
React는 아래와 같은 일이 발생하면 렌더링을 다시 수행한다.
setState/useState의 setter 호출useReducer의dispatch호출- 부모 컴포넌트가 다시 렌더링되어 자식이 함께 평가되는 경우
props가 변경되는 경우context값이 변경되는 경우
중요한 점은 “렌더링”이 곧바로 DOM을 바꾸는 의미가 아니라는 것이다.
React에서 렌더링은 우선 UI를 계산하는 단계이며, 실제 DOM 반영은 별도의 단계에서 일어난다.
1) Render Phase: UI를 계산하는 단계
렌더 단계(Render Phase)는 “현재 상태와 props를 기준으로, 화면이 어떤 구조가 되어야 하는가”를 계산한다.
컴포넌트 함수 호출
함수형 컴포넌트는 렌더링 시점에 호출된다.
- JSX를 반환하는 과정은 단순한 “함수 실행”에 가깝다.
- 이 단계에서 React는 “어떤 DOM을 만들지”를 직접 만들지 않고, 가상적인 결과물(React Element 트리)을 만든다.
즉, 렌더 단계의 결과는 다음과 같은 의미를 가진다.
- “이 상태라면 UI는 이런 모양이어야 한다”라는 설계도 생성
- 아직 브라우저 DOM은 건드리지 않음
render는 순수해야 한다
렌더 단계는 여러 번 실행될 수 있으며, 중간에 취소되거나 재시작될 수도 있다(특히 Concurrent 기능이 개입할 때).
따라서 렌더 함수(컴포넌트 본문)는 다음 조건을 지키는 것이 바람직하다.
- 외부 상태를 직접 변경하지 않는다.
- 네트워크 요청, 타이머 등록, DOM 조작 같은 부작용을 넣지 않는다.
- 같은 입력(props/state)이면 같은 결과(JSX)를 반환한다.
부작용은 useEffect와 같은 별도의 수단으로 분리하는 것이 React의 기본 전제에 가깝다.
2) Reconciliation: 변경점 비교 과정
React는 새로 계산된 결과(새 트리)와 이전 렌더링 결과(이전 트리)를 비교하여 “무엇이 바뀌었는지”를 찾는다.
이 비교 과정을 흔히 Reconciliation(재조정)이라고 부른다.
여기서 자주 언급되는 것이 Virtual DOM이다.
- Virtual DOM은 “실제 DOM을 직접 비교하지 않고”, 메모리 상의 트리 구조를 비교하는 방식에 가깝다.
- 비교 결과로 “어떤 부분을 업데이트해야 하는지”가 결정된다.
Key가 중요한 이유
리스트 렌더링에서 key는 단순 경고를 없애기 위한 값이 아니다.
React가 “이 항목은 같은 것인지, 새로 생긴 것인지”를 판단하는 기준이 된다.
- 안정적인
key를 주면 기존 DOM 재사용 가능성이 커진다. - 인덱스를
key로 쓰면 항목 삽입/삭제 시 의도치 않은 재사용이 발생할 수 있다.
3) Commit Phase: 실제 DOM 반영
변경점이 결정되면, 그 다음은 실제 화면에 반영하는 단계이다.
이를 Commit Phase라고 한다.
Commit 단계에서 일어나는 대표적인 작업은 다음과 같다.
- 실제 DOM 생성/수정/삭제
ref연결- 클래스 컴포넌트의 생명주기 일부 호출
- 브라우저에 “변경 사항을 반영”하도록 지시
렌더 단계가 “계산”이라면, 커밋 단계는 “적용”에 해당한다.
따라서 성능 최적화 관점에서는 렌더 단계에서 불필요한 계산을 줄이고, 커밋 단계에서 실제 DOM 변경을 최소화하는 것이 핵심이다.
4) Effect 처리: 화면 반영 이후의 작업
React는 커밋을 통해 화면을 반영한 뒤, 이른바 “부작용”을 처리한다.
useEffect
기본적으로 화면이 반영된 이후 실행된다.
- DOM이 그려진 뒤 실행되므로, 렌더 단계에 넣기 부적절한 작업을 여기서 처리한다.
- 예: 데이터 요청, 이벤트 구독, 타이머 등록/해제, 외부 라이브러리 연동 등
useLayoutEffect
커밋 이후이지만, 브라우저가 화면을 “그리기 직전”에 실행되는 성격이 강하다.
- DOM 측정이 필요한 경우(레이아웃 계산) 등에 쓰인다.
- 다만 남용하면 화면이 멈춘 것처럼 보일 수 있으므로 신중하게 사용한다.
렌더링 흐름을 한 문장으로 정리
React 렌더링은 다음 순서로 이해하는 것이 가장 깔끔하다.
- 상태/props 변화가 발생한다.
- 컴포넌트가 다시 실행되어(UI를) 계산한다. (Render Phase)
- 이전 결과와 새 결과를 비교하여 변경점을 찾는다. (Reconciliation)
- 필요한 DOM 변경만 실제로 적용한다. (Commit Phase)
- 화면 반영 이후 Effect를 실행한다. (Effects)
불필요한 렌더링을 줄이는 관점
React는 기본적으로 “빠르게 다시 그릴 수 있도록” 설계되어 있다.
그럼에도 렌더링 비용이 커지는 경우가 있으며, 그때는 아래 관점이 도움이 된다.
- 컴포넌트를 작게 쪼개서 변경 범위를 축소한다.
- 리스트 렌더링에서
key를 안정적으로 유지한다. - 비싼 연산은
useMemo로 “계산 비용”을 줄인다. - 자식이 불필요하게 다시 렌더링된다면
React.memo를 고려한다. - 콜백을 자식에게 많이 내려보낸다면
useCallback로 참조 안정성을 확보한다.
다만 최적화는 “측정 후 적용”이 원칙이며, 단순히 훅을 남용하는 것은 오히려 복잡도만 키울 수 있다.
마무리
React의 렌더링은 “DOM을 계속 갈아끼우는 방식”이 아니라,
UI를 계산하고, 변경점을 찾고, 필요한 만큼만 적용하는 과정이다.
이 흐름을 이해하면 다음과 같은 질문에 답하기 쉬워진다.
- 왜 state 변경이 즉시 DOM 변경과 같지 않은가
- 왜 렌더 함수에 부작용을 넣으면 문제가 되는가
- 왜 key가 렌더링 성능과 버그에 직접 영향을 주는가
React의 성능과 동작을 안정적으로 다루기 위해서는, 렌더링을 “그림을 그리는 행위”가 아니라 “변경을 관리하는 절차”로 보는 관점이 유효하다.