React - 스냅샷으로서의 State

Sally·2026년 2월 26일

State 변수는 읽고 쓸 수 있는 일반 자바스크립트 변수처럼 보일 수 있지만 react에서 state는 스냅샷처럼 동작한다. state 변수를 설정하여도 이미 가지고 있는 state 변수는 변경되지 않고, 대신 리렌더링이 발동된다.

state를 설정하면 렌더링이 동작한다

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) {
  // ...
}
  • React에서 인터페이스가 이벤트에 반응하려면 state를 업데이트해야한다. 위의 예시에서는 "send"를 누르면 setIsSent(true) 는 React에 UI를 다시 렌더링하도록 지시한다.

버튼 클릭 시 발생하는 일
1. onSubmit 이벤트 핸들러가 실행됨
2. sentIsSent(true)isSenttrue로 설정하고 새로운 렌더링을 큐에 넣음
3. React는 새로운 isSent 값에 따라 컴포넌트를 다시 렌더링함.

렌더링은 그 시점의 스냅샷을 찍는다

  • 렌더링 이란 React가 컴포넌트, 즉 함수를 호출한다는 뜻이다. 해당 함수에서 반환하는 JSX는 시간상 UI의 스냅샷과 같은데, prop, 이벤트 핸들러, 로컬 변수는 모두 렌더링 당시의 state를 사용해 계산된다.

  • React가 컴포넌트를 다시 렌더링할 때,

    1. React가 함수를 다시 호출
    2. 함수가 새로운 JSX 스냅샷을 반환
    3. React가 함수가 반환한 스냅샷과 일치하도록 화면 업데이트

컴포넌트의 메모리로써 state는 함수가 반환된 후 사라지는 일반 변수와 다르다. state는 실제로 함수 외부에 마치 선반에 있는 것처럼 React 자체에 존재한다.

Q) 함수가 끝나도 사라지지 않는 선반 ?

  • 보통 자바스크립트 함수 안에 선언한 변수는 함수 실행이 끝나면 메모리에서 사라진다. 하지만, 리액트 컴포넌트는 조금 다르다.
    • 일반 변수: 함수가 실행될 때마다 초기화된다. (매번 0부터 시작)
    • State: 리액트라는 '관리자'가 컴포넌트 외부의 특정 선반(메모리 공간) 에 보관해 둔다.
    • 컴포넌트 호출: 리액트가 "자, 이번 렌더링 차례야! 네 선반에 있던 값은 '5'야, 여기 있어." 하고 전달해 주는 방식

Q) 특정 렌더링의 스냅샷 ?

  • 리액트에서 렌더링은 그 순간의 사진 촬영과 같다고 했는데,
    • 컴포넌트 함수가 호출되는 순간, 리액트는 그 시점의 state 값을 고정해서 함수에 던져준다.
    • 함수 내부에서 아무리 setState를 호출해도, 이미 찍힌 사진(현재 실행 중인 함수 내부) 속의 state 값은 변하지 않는다. 다음 사진(다음 렌더링)에서나 바뀐 값이 적용된다.

컴포넌트: "나는 리액트가 주는 데이터(State)를 받아서, 화면 설계도(JSX)를 그려서 돌려주는 함수일 뿐이야."
리액트: "데이터는 내가 내 선반에 잘 보관해둘게. 네가 설계도를 주면 내가 이전 거랑 비교해서 바뀐 부분만 공사(DOM 반영)해 줄게!

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를 설정하면 다음 렌더링에 대해서만 변경된다

  • 위의 코드를 보고 이벤트 핸들러가 완료된 후 React가 컴포넌트 안의 number을 3으로 렌더링한다고 생각할 수 있지만, 위의 코드에서 항상 number은 1로 렌더링된다.

  • 이 버튼의 클릭 핸들러가 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로 변경할 준비를 합니다.
    2. setNumber(number + 1): number는 0이므로 setNumber(0 + 1)입니다. -> React는 다음 렌더링에서 number를 1로 변경할 준비를 합니다.
<button onClick={() => {
  setNumber(0 + 1);
  setNumber(0 + 1);
  setNumber(0 + 1);
}}>+3</button>

시간 경과에 따른 state

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>
    </>
  )
}
  • +5라고 표시된 버튼을 누르면 3초 후에 alert(알림창)이 뜨는데, 이 알림창에는 5가 아니라 0이 찍힌다.

1. 첫 번째 렌더링

  • 처음 화면이 그려질 때, 리액트는 number 가 0인 상태로 이 컴포넌트의 사진을 찍는다.
    • <h1> 에는 0이 들어간다.
    • onClick 이벤트 핸들러 안의 number도 0으로 고정된다. 즉, 코드는 이미 내부적으로 아래와 같이 박제된 상태이다.
setNumber(0 + 5);
setTimeout(() => {
  alert(0); // number가 아니라 0이 들어있는 것과 같음
}, 3000);

2. 버튼을 눌렀을 때
1. setNumber(0+5)실행 : 리액트에게 '다음 렌더링 때는 number을 5로 바꿔달라고 요청하면, 리액트는 재렌더링을 준비함
2. setTimeout 실행 : 3초 뒤에 alert(number)을 띄우라고 예약함. 하지만 여기서 number은 현재 렌더링 스냅샷인 '0'

3. 화면 업데이트와 알림창의 차이

  • 화면: 리액트가 재렌더링을 마치면 <h1>은 5로 바뀜. 사용자는 화면에 5가 적힌 것을 볼 수 있음
  • 알림창: 3초가 지나면 아까 예약했던 aler 가 실행됨. 이 함수는 3초 전 (number=0) 만들어진 함수이기 때문에 알림창은 0을 출력함

React는 렌더링의 이벤트 핸들러 내에서 state 값을 “고정”으로 유지한다. 코드가 실행되는 동안 state가 변경되었는지를 걱정할 필요가 없다.

0개의 댓글