React/공식 문서

렌더링과 State 업데이트

favor 2026. 4. 8. 14:32

렌더링 그리고 커밋

컴포넌트를 화면에 표시하기 위해서는 렌더링 필요

1. 렌더링 트리거 (손님의 주문을 주방으로 전달)
2. 컴포넌트 렌더링 (주방에서 주문 준비하기)
3. DOM에 커밋 (테이블에 주문한 요리 내놓기)

 

✔️ 1단계: 렌더링 트리거

컴포넌트 렌더링이 일어나는 두 가지 이유

  1. 컴포넌트의 초기 렌더링인 경우
  2. 컴포넌트의 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가 컴포넌트를 다시 렌더링할 때

  1. React가 함수를 다시 호출
  2. 함수가 새로운 JSX 스냅샷을 반환
  3. 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>
    </>
  )
}

number는 클릭당 한 번만 증가

 

해당 버튼의 클릭 핸들러가 React에 지시하는 작업

  1. setNumber(number + 1) : number는 0 --> setNumber(0 + 1)
    • React는 다음 렌더링에서 number를 1로 변경할 준비를 함
  2. setNumber(number + 1) : number는 0 --> setNumber(0 + 1)
    • React는 다음 렌더링에서 number를 1로 변경할 준비를 함
  3. 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)

  1. React는 이벤트 핸들러의 다른 코드가 모두 실행된 후에 이 함수가 처리되도록 큐에 넣음
  2. 다음 렌더링 중에 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에 지시하는 작업

  1. setNumber(number + 5)
    • number는 0 --> setNumber (0 + 5)
    • React는 큐에 '5로 바꾸기'를 추가
  2. 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에 지시하는 작업

  1. setNumber(number + 5)
    • number는 0 --> setNumber(0 + 5)
    • React는 '5로 바꾸기'를 큐에 추가
  2. setNumber(n => n + 1)
    • n => n + 1는 업데이터 함수
    • React는 해당 함수를 큐에 추가
  3. 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