State 변수는 읽고 쓸 수 있는 일반 자바스크립트 변수처럼 보일 수 있지만 react에서 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) {
// ...
}
setIsSent(true) 는 React에 UI를 다시 렌더링하도록 지시한다. 버튼 클릭 시 발생하는 일
1. onSubmit 이벤트 핸들러가 실행됨
2. sentIsSent(true) 가 isSent 를 true로 설정하고 새로운 렌더링을 큐에 넣음
3. React는 새로운 isSent 값에 따라 컴포넌트를 다시 렌더링함.

렌더링 이란 React가 컴포넌트, 즉 함수를 호출한다는 뜻이다. 해당 함수에서 반환하는 JSX는 시간상 UI의 스냅샷과 같은데, prop, 이벤트 핸들러, 로컬 변수는 모두 렌더링 당시의 state를 사용해 계산된다.
React가 컴포넌트를 다시 렌더링할 때,
컴포넌트의 메모리로써 state는 함수가 반환된 후 사라지는 일반 변수와 다르다. state는 실제로 함수 외부에 마치 선반에 있는 것처럼 React 자체에 존재한다.
컴포넌트: "나는 리액트가 주는 데이터(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에 지시하는 작업은 다음과 같다
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로 변경할 준비를 합니다.<button onClick={() => {
setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);
}}>+3</button>
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가 적힌 것을 볼 수 있음aler 가 실행됨. 이 함수는 3초 전 (number=0) 만들어진 함수이기 때문에 알림창은 0을 출력함React는 렌더링의 이벤트 핸들러 내에서 state 값을 “고정”으로 유지한다. 코드가 실행되는 동안 state가 변경되었는지를 걱정할 필요가 없다.