상태 변수는 읽고 쓸 수 있는 일반 JavaScript 변수처럼 보일 수 있습니다. 그러나 상태는 스냅샷처럼 동작합니다. 이를 설정하면 이미 가지고 있는 상태 변수가 변경되지 않고 대신 리렌더링이 트리거됩니다.
클릭과 같은 사용자 이벤트에 대한 응답으로 사용자 인터페이스가 직접 변경되는 것으로 생각할 수 있습니다. React에서는 약간 다르게 작동합니다. 이전 페이지에서 상태 설정이 React에서 리렌더링을 요청하고 이는 인터페이스가 이벤트에 반응하려면 상태를 업데이트 해야 함을 의미합니다.
이 예에서 “send”를 누르면 setIsSent(true) 가 React에 리렌더링을 전달합니다.
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) {
// ...
}
버튼을 클릭하면 다음과 같은 일이 발생합니다.
onsubmit이벤트 핸들러가 실행됩니다.setIsSent(true)가isSent를true로 설정하고 새로운 렌더링 대기열에 넣습니다.- React는 새로운
isSent값을 통하여 컴포넌트를 리렌더링합니다.
상태와 렌더링의 관계를 자세히 살펴보겠습니다.
“렌더링”은 React가 함수인 컴포넌트를 호출한다는 것을 의미합니다. 해당 함수에서 반환하는 JSX는 시간에 따른 UI의 스냅샷과 같습니다. props, 이벤트 핸들러 및 지역 변수는 모두 렌더링 당시의 상태를 사용하여 계산되었습니다.
사진이나 동영상 프레임과 달리 반환되는 UI “스냅샷”은 대화형입니다. 여기에는 입력에 대한 응답으로 발생하는 작업을 지정하는 이벤트 핸들러와 같은 논리가 포함됩니다. React는 이 스냅샷과 일치하도록 화면을 업데이트하고 이벤트 핸들러를 연결합니다. 결과적으로 버튼을 누르면 JSX에서 클릭 핸들러가 트리거 됩니다.
React가 컴포넌트를 다시 렌더링할 때:
- React는 함수를 다시 호출합니다.
- 함수는 새로운 JSX 스냅샷을 반환합니다.
- 그런 다음 React는 함수가 반환한 스냅샷과 일치하도록 화면을 업데이트합니다.

컴포넌트의 메모리로서 상태는 함수가 반환된 후 사라지는 일반 변수와는 다릅니다. 상태는 실제로 마치 선반위에 있는 것처럼 React 자체에 (함수 외부에) “살아 있습니다.” React가 컴포넌트를 호출하면 특정 렌더링 상태에 대한 스냅샷을 제공합니다. 컴포넌트는 JSX의 새로운 props 및 이벤트 핸들러 세트와 함께 UI의 스냅샷을 반환하며, 모두 해당 렌더링의 상태 값을 사용하여 계산됩니다.

이것이 어떻게 작동하는지 보여주는 작은 실험이 있습니다. 이 예에서는 "+3" 버튼을 클릭하면 setNumber(number + 1) 를 세 번 호출되므로 카운터가 세 번 증가할 것으로 예상할 수 있습니다.
“+3” 버튼을 클릭하면 어떤 일이 일어나는지 확인하세요.
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 클릭 당 1씩 증가합니다.
상태 설정은 다음 렌더링에 대해서만 변경됩니다. 첫 번째 렌더링 중에는 number가 0이었습니다. 이것은 onClick 핸들러의 해당 렌더링에서 setNumber(number + 1) 가 호출되었을 때 number의 값이 0인 이유입니다.
다음은 이 버튼의 onClick 핸들러가 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이므로 상태를 1로 세 번 설정합니다. 이것이 바로 이벤트 핸들러가 완료된 후 React가 number와 같은 컴포넌트를 1이 아닌 3으로 다시 렌더링하는 이유입니다.
// 초기 렌더링
<button onClick={() => {
setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);
}}>+3</button>
// 두 번째 렌더링
<button onClick={() => {
setNumber(1 + 1);
setNumber(1 + 1);
setNumber(1 + 1);
}}>+3</button>
이것 이바로 버튼을 다시 클릭하면 카운터가 2로 설정되고 그 다음 클릭에서는 3으로 설정되는 이유입니다.
버튼을 눌렀을 때 경고되는 내용을 예상해 볼 수 있습니다.
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>
</>
)
}
이전의 방법을 사용하면 경고에 “0”이 표시되는 것으로 추측할 수 있습니다.
하지만 경고에 타이머를 설정하여 컴포넌트가 다신 렌더링된 후에 실행된다면 어떻게 될까요?
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>
</>
)
}
React에 저장된 상태는 알림이 실행될 때 변경되었을 수 있지만 사용자가 상호 작용할 때 상태의 스냅샷을 사용하여 예약되었습니다.
이벤트 핸들러의 코드가 비동기적이더라도 상태 변수의 값은 렌더링 내에서 절대 변경되지 않습니다. 해당 렌더링 내부에서 setNumber(number + 5) 이 호출된 후에도 onClick 값이 number는 0으로 계속 유지됩니다. React가 컴포넌트를 호출하여 UI의 “스냅샷”을 찍을 때 그 값은 “고정”되었습니다.
다음은 이벤트 핸들러의 타이밍 실수를 줄이는 방법에 대한 예입니다. 다음은 5초 지연하여 메시지를 보내는 양식입니다.
- “보내기”버튼을 누르면 “Hello”가 Alice에게 전송됩니다.
- 5초 지연이 끝나기 전에 “받는 사람” 필드의 값을 “Bob”으로 변경합니다.
import { useState } from 'react';
export default function Form() {
const [to, setTo] = useState('Alice');
const [message, setMessage] = useState('Hello');
function handleSubmit(e) {
e.preventDefault();
setTimeout(() => {
alert(`You said ${message} to ${to}`);
}, 5000);
}
return (
<form onSubmit={handleSubmit}>
<label>
To:{' '}
<select
value={to}
onChange={e => setTo(e.target.value)}>
<option value="Alice">Alice</option>
<option value="Bob">Bob</option>
</select>
</label>
<textarea
placeholder="Message"
value={message}
onChange={e => setMessage(e.target.value)}
/>
<button type="submit">Send</button>
</form>
);
}
React는 한 렌더링의 이벤트 핸들러 내에서 상태 값을 “고정”으로 유지합니다. 코드가 실행되는 동안 상태가 변경되지 않습니다.