상태 변수는 읽고 쓸 수 있는 일반적인 자바스크립트 변수처럼 생겼어요. 그러나 상태는 스탭샷에 더 가깝게 행동해요. 상태를 지정하면 이미 갖고 있는 상태 변수를 바꾸는 것이 아니라 리렌더링을 발생시켜요.
이 페이지에서는
- 상태를 정하는 것이 리렌더링을 어떻게 발생시키는지
- 상태는 언제 어떻게 업데이트되는지
- 왜 상태는 설정한 즉시 업데이트 되지 않는지
- 이벤트 핸들러는 상태의 "스냅샷"에 어떻게 접근하는지
를 알아볼 거예요.
Setting state triggers renders | 상태를 설정하면 렌더링을 유발해요
클릭과 같은 사용자 이벤트에 맞추어 즉시 사용자 인터페이스가 바뀐다고 생각할 수 있어요. 리액트는 이러한 멘탈 모델과는 조금 다르게 동작해요. 이전 페이지에서 상태를 설정하면 리렌더링을 요청한다는 것을 살펴보았어요. 이는 즉, 인터페이스가 이벤트에 반응하려면 상태를 업데이트 해야한다는 것이에요.
이 예시에서는 "send" 버튼을 누르면 setIsSent(true)
가 UI를 리렌더링하라고 리액트에게 알려줘요.
버튼을 누르면 아래와 같은 일들이 일어나요.
onSubmit
이벤트 핸들러가 동작합니다.setIsSent(true)
는isSent
를true
로 설정하고 새로운 렌더링을 대기열에 넣어요.- 리액트는 새로운
isSent
값에 따라 컴포넌트를 리렌더링해요.
상태와 렌더링 간의 관계에 대해서 더 자세히 알아볼게요.
Rendering takes a snapshot in time | 렌더링은 순간의 스냅샷을 가져와요.
"렌더링"은 리액트가 함수인 컴포넌트를 호출하는 것을 말해요. 함수에서 반환한 JSX는 그 시간의 UI 스냅샷과 같아요. props, 이벤트 핸들러 그리고 지역변수는 렌더링을 할 때 이 상태를 사용하여 계산돼요.
사진이나 영화 프레인과는 달리 반환한 UI "스냅샷"은 상호작용적이에요. 이 스냅샷은 입력값이 들어오면 발생하는 일들을 지정하는 이벤트 핸들러와 같은 로직을 포함해요. 리액트는 화면을 스냅샷에 맞게 업데이트하고 이벤트 핸들러를 연결해요. 결론적으로 버튼을 누르는 것은 JSX에서 클릭 핸들러를 발생시켜요.
리액트가 컴포넌트를 리렌더링 할 때,
- 리액트는 함수를 다시 호출하고,
- 함수는 새로운 JSX 스냅샷을 반환해요.
- 그리고나면 리액트는 함수가 반환한 스냅샷에 맞게 화면을 업데이트해요.





Illustrated by Rachel Lee Nabors (출처: react.dev)
컴포넌트의 저장공간으로서, 상태는 함수가 반환되면 사라지는 일반 변수와는 달라요. 상태는 실제로 마치 선반에 있는 듯 리액트 안, 함수의 바깥에서 "살아있어요." 리액트가 컴포넌트를 호출할 때 특정 렌더링에 대한 상태의 스냅샷을 줘요. 컴포넌트는 JSX 안에서 렌더링을 할 때 상태를 사용하여 계산되는 새로운 props와 이벤트 핸들러들과 함께 UI의 스냅샷을 반환해요.





Illustrated by Rachel Lee Nabors (출처: react.dev)
아래의 예시는 어떻게 동작하는지를 보여주는 작은 실험이에요. 이 예시에서 "+3" 버튼을 누르면 setNumber(number + 1)
을 세 번 호출하기 때문에 카운터를 3번 증가시킬 거예요.
"+3" 버튼을 클릭하면 어떤 일이 일어나는지 보세요.
number
은 클릭을 할 때마다 1씩만 증가하는 걸 확인하세요.
상태를 설정하는 것은 다음 렌더링에 그것을 바꿔줘요. 첫 번째 렌더링에서 number
은 0
이었어요. 렌더링의 onClick
핸들러에 있는 number
가 setNumber(number + 1)
이 호출되었음에도 여전히 0
이기 때문이에요.
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>
버튼의 클릭 핸들러가 리액트에게 알려주는 해야할 일이에요.
setNumber(number + 1)
:number
이0
이기 때문에setNumber(0 + 1)
을 알려줘요.- 리액트는
number
을 다음 렌더링에서1
로 바꿀 생각을 해요.
- 리액트는
setNumber(number + 1)
:number
이0
이기 때문에setNumber(0 + 1)
을 알려줘요.- 리액트는
number
을 다음 렌더링에서1
로 바꿀 생각을 해요.
- 리액트는
setNumber(number + 1)
:number
이0
이기 때문에setNumber(0 + 1)
을 알려줘요.- 리액트는
number
을 다음 렌더링에서1
로 바꿀 생각을 해요.
- 리액트는
setNumber(number + 1)
을 3번 호출했음에도 불구하고 이번 렌더링의 이벤트 핸들러에서의 number
는 항상 0
이기 때문에 상태는 1
로 세 번 바뀌어요. 이것이 바로 이벤트 핸들러가 종료된 후에, 리액트가 컴포넌트를 3
이 아니라 1
로 설정된 number
로 컴포넌트를 렌더링하는 이유예요.
코드에서 상태변수를 해당 값으로 대체하면 시각화할 수 있어요. 이번 렌더링에서 number
상태 변수가 0
이기 때문에 이벤트 핸들러는 아래와 같이 생겼어요.
<button onClick={() => {
setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);
}}>+3</button>
다음 렌더링에서 number
는 1
이기 때문에 이번 렌더링의 클릭 핸들러는 아래와 같아요.
<button onClick={() => {
setNumber(1 + 1);
setNumber(1 + 1);
setNumber(1 + 1);
}}>+3</button>
이것이 바로 버튼을 다시 클릭하면 카운터가 2
로, 그리고 그 다음엔 3
으로 설정되는 이유예요.
State over time | 시간에 따른 상태
이건 조금 흥미로운데요. 이 버튼을 클린하면 어떤 alert가 뜨는지 추측해보세요.
만약 이전과 같은 대체 메서드를 사용한다면 alert가 "0"을 보여줄 것임을 추측할 수 있어요.
setNumber(0 + 5);
alert(0);
그러나 만약 타이머를 alert에 추가한다면 컴포넌트가 리렌더링된 이후에만 실행할까요? "0"이라고 할까요, "5"라고 할까요? 생각해보세요!
놀랐나요? 만약 대체 메서드로 나타내본다면 상태의 "스냅샷"이 alert에 전달되었다는 사실을 볼 수 있어요.
setNumber(0 + 5);
setTimeout(() => {
alert(0);
}, 3000);
리액트에 저장된 상태는 alert가 실행될때 바뀌어요. 하지만 사용자가 상태와 상호작용을 할 때의 스냅샷을 사용하도록 스케줄링 되어있어요!
상태 변수의 값은 렌더링 동안에는 절대 바뀌지 않아요. 설령 이벤트 핸들러 코드가 비동기 코드더라도 이는 바뀌지 않아요. 해당 렌더링의 onClick
안에서 number
의 값은 setNumber(number + 5)
가 호출된 후에도 계속 '0'이에요. 리액트가 컴포넌트를 호출아혀 UI의 "스냅샷을 사용할 때" 그 값은 "변경돼요."
이벤트 핸들러가 타이밍 실수를 할 가능성을 줄이는 방법을 보여주는 예시가 있어요. 아래는 5초간의 딜레이 후에 메시지를 전송하는 폼이에요. 이 시나리오를 생각해보세요.
- "Hello"를 Alice에게 보내는 "Send" 버튼을 누르세요.
- 5초의 딜레이가 끝나기 전에, "To"의 입력창 값을 "Bob"으로 바꾸세요.
alert
에서 어떤 것이 보여질까요? "You said Hello to Alice"가 보여질까요, 아니면 "You saied Hello to Bob"이 보여질까요? 여러분이 알고 있는 지식을 바탕으로 생각해본 후에 시도해보세요.
리액트는 한 렌더링의 이벤트 핸들러 안에서 상태 값을 "변경해요." 코드가 실행되는 동안 상태가 변할까 염려하지 않아도 돼요.
하지만 만약 리렌더링 전에 최근 상태를 읽고 싶다면 어떡할까요? 다음 장에서 배울 상태 업데이트 함수를 사용하면 돼요!
Recap | 요약
- 상태를 지정하면 새 렌더링이 요청된다.
- 리액트는 컴포넌트 외부에서 마치 선반에 놓는 것마냥 상태를 저장한다.
useState
함수를 호출할 때 리액트는 해당 렌더링에 맞는 상태의 스냅샷을 줘요.- 변수와 이벤트 핸들러를 리렌더링에서 "살아남지" 않아요. 모든 렌더링은 각자의 이벤트 핸들러를 가져요.
- 모든 렌더링(그리고 그 안의 함수)은 항상 리액트가 해당 렌더링에 넘겨준 상태의 스냅샷을 봐요.
- 렌더링된 JSX를 생각하는 방식과 비슷하게 이벤트 핸들러 안에서 상태를 대체할 수 있어요.
- 이전에 생성된 이벤트 핸들러는 그들이 생성되는 렌더링에서 온 상태 변수를 가지고 있어요.
Challenges | 도전 과제
1. Implement a traffic light | 신호등 구현하기
버튼이 눌리면 토글되는 신호등 컴포넌트가 있어요.
alert
를 이벤트 핸들러에 추가하세요. 신호등이 초록색이고 "Walk"라고 보일 때 버튼을 클릭하면 "Stop is next"라고 보여야해요. 신호등이 빨간색이고 "Stop"라고 보일 때 버튼을 클릭하면 "Walk is next"라고 보여야해요.
alert
가 setWalk
를 호출하기 전에 넣는 것과 후에 넣는 것의 차이가 있나요?
Solution
alert
는 아래와 같아야해요.
alert
를 setWalk
호출 앞에 넣든지 뒤에 넣든지 차이는 없어요. 해당 렌더링의 walk
값은 정해졌어요. setWork
를 호출하면 해당 상태는 다음 렌더링에서 바뀌어요. 하지만 이전 렌더링의 이벤트 핸들러에 영향은 없어요.
처음에는 아래의 코드가 반 직관적으로 보일 거예요.
alert(walk ? 'Stop is next' : 'Walk is next');
그러나 만약 여러분이 이것을 "만약 신호등이 "Walk now"를 보여준다면 메시지는 "Stop is next".를 보여줘야한다고 생각하면 이해가 될거에요. 이벤트 핸들러 안의 walk
변수는 walk
의 렌더링 값과 일하고 바뀌지 않아요.
이것이 맞았는지는 대체 메서드를 적용하면 알 수 있어요. 만약 walk
가 true
라면, 아래와 같은 코드가 될 거예요.
<button onClick={() => {
setWalk(false);
alert('Stop is next');
}}>
Change to Stop
</button>
<h1 style={{color: 'darkgreen'}}>
Walk
</h1>
따라서 "Change to Stop"은 walk
가 false
로 설정된 상태로 대기열에 들어가고 "Stop is next"를 alert에 띄울 거예요.
'리액트 공식문서 | React Docs > Learn > Learn React' 카테고리의 다른 글
[Adding Interactivity] Updating Objects in State | 상태에서 객체 업데이트하기 (0) | 2024.02.21 |
---|---|
[Adding Interactivity] Queueing a Series of State Updates | 일련의 상태 업데이트를 대기열에 넣기 (0) | 2024.02.19 |
[Adding Interactivity] Render and Commit | 렌더링과 커밋 (2) | 2024.02.18 |
[Adding Interactivity] State: A Component's Memory | 상태: 컴포넌트의 메모리 (2) | 2024.02.16 |
[Adding Interactivity] Responding to Events | 이벤트에 반응하기 (0) | 2024.02.15 |