렌더링 그리고 커밋
컴포넌트를 화면에 표시하기 위해서는 렌더링 필요
1. 렌더링 트리거 (손님의 주문을 주방으로 전달)
2. 컴포넌트 렌더링 (주방에서 주문 준비하기)
3. DOM에 커밋 (테이블에 주문한 요리 내놓기)
✔️ 1단계: 렌더링 트리거
컴포넌트 렌더링이 일어나는 두 가지 이유
- 컴포넌트의 초기 렌더링인 경우
- 컴포넌트의 state가 업데이트된 경우
✔️ 초기 렌더링
앱을 시작 --> 초기 렌더링을 트리거
// index.js
import Image from './Image.js';
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'))
root.render(<Image />);
createRoot를 호출 --> 해당 컴포넌트로 render 메소드를 호출 --> 렌더링
✔️ State 업데이트 시 리렌더링
초기 렌더링 이후에는 set 함수를 통해 상태를 업데이트하여 추가적인 렌더링 가능
컴포넌트의 상태를 업데이트 --> 자동으로 렌더링 대기열에 추가
✔️ 2단계: React 컴포넌트 렌더링
렌더링 트리거 이후에는 컴포넌트를 호출하여 화면에 표시할 내용을 파악
렌더링 : React에서 컴포넌트를 호출하는 것
- 초기 렌더링에서 React는 루트 컴포넌트를 호출
- 이후 렌더링에서 React는 state 업데이트가 일어나 렌더링을 트리거한 컴포넌트를 호출
* 재귀적 단계
업데이트된 컴포넌트가 다른 컴포넌트를 반환
--> React는 다음으로 해당 컴포넌트를 렌더링
--> 해당 컴포넌트도 컴포넌트를 반환
--> 반환된 컴포넌트를 다음에 렌더링
ㄴ 중첩된 컴포넌트가 더이상 없고, React가 화면에 표시되어야 하는 내용을 정확히 알 때까지 단계를 계속 진행
✔️ 렌더링의 순수함
- 동일한 입력에는 동일한 출력을 해야 함
- 동일한 입력이 주어지면, 컴포넌트는 항상 동일한 JSX를 반환해야 함
- 이전의 state를 변경해서는 안됨
- 렌더링 전에 존재했던 객체나 변수를 변경해서는 안됨
✔️ 3단계: React가 DOM에 변경사항을 커밋
컴포넌트를 렌더링(호출)한 후, React는 DOM을 수정
- 초기 렌더링의 경우
- React는 appendChild() DOM API를 사용하여 생성한 모든 DOM 노드를 화면에 표시
- 리렌더링의 경우
- React는 필요한 최소한의 작업을 적용하여 DOM이 최신 렌더링 출력과 일치하도록 함
React는 렌더링 간에 차이가 있는 경우에만 DOM 노드를 변경
= 렌더링 결과가 이전과 같으면 React는 DOM을 건드리지 않음
✔️ 에필로그: 브라우저 페인트
렌더링 완료 --> React가 DOM을 업데이트 --> 브라우저가 화면을 다시 그림
브라우저가 화면을 그리는 것 : 브라우저 렌더링 (페인팅)
* React Rendering ≠ Browser Painting
React는 DOM이라는 구조만 바꿀 뿐, 실제 화면에 그림을 그려서 시각화하는 최종 단계는 브라우저의 페인팅 작업
스냅샷으로서의 State
state 변수는 스냅샷처럼 동작
ㄴ 리액트가 컴포넌트를 실행하는 순간, 그 시점의 state 값들을 모아서 사진(스냅샷)을 찍는다는 의미
state 변수를 설정하여도 이미 가지고 있는 state 변수는 변경되지 않고, 대신 리렌더링이 발동
✔️ state를 설정하면 렌더링이 동작
인터페이스가 이벤트에 반응하려면 state를 업데이트해야 함
// App.js
import { useState } from 'react';
export default function Form() {
const [isSent, setIsSent] = useState(false);
const [message, setMessage] = useState('Hi!');
if (isSent) {
return <h1>Your message is on its way!</h1>
}
return (
<form onSubmit={(e) => {
e.preventDefault();
setIsSent(true);
sendMessage(message);
}}>
<textarea
placeholder="Message"
value={message}
onChange={e => setMessage(e.target.value)}
/>
<button type="submit">Send</button>
</form>
);
}
function sendMessage(message) {
// ...
}
ㄴ 'send'를 누르면 setIsSent(true)는 React에 UI를 다시 렌더링하도록 지시
✔️ 렌더링은 그 시점의 스냅샷을 찍음
렌더링 : React가 컴포넌트, 즉 함수를 호출한다는 의미
ㄴ 해당 함수에서 반환하는 JSX는 시간상 UI의 스냅샷과 같음
= prop, 이벤트 핸들러, 로컬 변수는 모두 렌더링 당시의 state를 사용해 계산됨
React가 컴포넌트를 다시 렌더링할 때
- React가 함수를 다시 호출
- 함수가 새로운 JSX 스냅샷을 반환
- React가 함수가 반환한 스냅샷과 일치하도록 화면을 업데이트
state는 함수가 반환된 후 사라지는 일반 변수와는 다름
ㄴ state는 React에 '존재'함
// App.js
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>
</>
)
}

해당 버튼의 클릭 핸들러가 React에 지시하는 작업
- setNumber(number + 1) : number는 0 --> setNumber(0 + 1)
- React는 다음 렌더링에서 number를 1로 변경할 준비를 함
- setNumber(number + 1) : number는 0 --> setNumber(0 + 1)
- React는 다음 렌더링에서 number를 1로 변경할 준비를 함
- setNumber(number + 1) : number는 0 --> setNumber(0 + 1)
- React는 다음 렌더링에서 number를 1로 변경할 준비를 함
setNumber(number + 1)를 세 번 호출했지만, 이 렌더링에서 이벤트 핸들러에서 number는 항상 0
--> number는 항상 0이므로 state를 1로 세 번 설정
✔️ 시간 경과에 따른 State
// App.js
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 5);
alert(number);
}}>+5</button>
</>
)
}


setNumber(0 + 5);
alert(0);
// App.js
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 5);
setTimeout(() => {
alert(number);
}, 3000);
}}>+5</button>
</>
)
}


setNumber(0 + 5);
setTimeout(() => {
alert(0);
}, 3000);
state 변수의 값은 한 번의 렌더링 내에서는 절대 변경되지 않음
= state 값은 컴포넌트를 호출해 React가 UI의 스냅샷을 찍을 때 고정된 값
ㄴ setTime이나 await 등과 같은 비동기 함수 안에서도 마찬가지
State 업데이트 큐
state 변수 설정 --> 다음 렌더링이 큐에 들어감
✔️ React state batches 업데이트
// App.js
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>
</>
)
}
각 렌더링의 state 값은 고정되어 있기 때문에 setNumber()를 몇 번 호출하든 number 값은 항상 0
위와 같은 결과가 나타나는 데에는 한 가지 요인이 더 있는데,
React는 state 업데이트를 하기 전에 이벤트 핸들러의 모든 코드가 실행될 때까지 기다린다는 것
= 리렌더링은 모든 setNumber() 호출이 완료된 이후에만 발생
batching : 여러 업데이트를 하나로 묶어서 이벤트 핸들러가 끝날 때 리렌더링을 수행
✔️ 다음 렌더링 전에 동일한 State 변수를 여러 번 업데이트하기
다음 렌더링 전에 동일한 state 변수를 여러 번 업데이트하고 싶다면,
setNumber(number + 1) (X)
setNumber(n => n + 1) (O)
// App.js
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
}}>+3</button>
</>
)
}
n => n + 1 : 업데이터 함수 (updater function)
- React는 이벤트 핸들러의 다른 코드가 모두 실행된 후에 이 함수가 처리되도록 큐에 넣음
- 다음 렌더링 중에 React는 큐를 순회하여 최종 업데이트된 state를 제공
✔️ State를 교체한 후 업데이트하면 어떻게 되나요?
// App.js
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 5);
setNumber(n => n + 1);
}}>Increase the number</button>
</>
)
}

위 이벤트 핸들러가 React에 지시하는 작업
- setNumber(number + 5)
- number는 0 --> setNumber (0 + 5)
- React는 큐에 '5로 바꾸기'를 추가
- setNumber(n => n + 1)
- n => n + 1는 업데이터 함수
- React는 해당 함수를 큐에 추가
✔️ 업데이트 후 State를 바꾸면 어떻게 되나요?
// App.js
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 5);
setNumber(n => n + 1);
setNumber(42);
}}>Increase the number</button>
</>
)
}

위 이벤트 핸들러가 React에 지시하는 작업
- setNumber(number + 5)
- number는 0 --> setNumber(0 + 5)
- React는 '5로 바꾸기'를 큐에 추가
- setNumber(n => n + 1)
- n => n + 1는 업데이터 함수
- React는 해당 함수를 큐에 추가
- setNumber(42)
- React는 '42로 바꾸기'를 큐에 추가
✔️ 명명 규칙
업데이터 함수 인수의 이름은 해당 state 변수의 첫 글자로 지정하는 것이 일반적
- setEnabled(e => !e);
- setLastName(ln => ln.toUpperCase());
- setFriendCount(fc => fc * 2);
'React > 공식 문서' 카테고리의 다른 글
| useReducer (0) | 2026.05.21 |
|---|---|
| 상태 구조 설계와 공유 (0) | 2026.05.07 |
| 불변 업데이트 패턴 (0) | 2026.04.28 |
| 이벤트 처리와 State (0) | 2026.04.02 |
| Props와 리스트 렌더링 (0) | 2026.03.25 |