리액트의 상태 변수가 그냥 일반적인 자바스크립트 변수처럼 보이지만 상태는 하나의 스냅샷에 더 가깝다. setState
함수를 이용하여 상태를 업데이트하는 것은 현재의 상태를 변경시키지 않고 리렌더링을 일으킨다.
이번 포스팅에서 얻을 수 있는 부분은..
유저가 웹페이지 상에서 마우스 클릭 이벤트를 일으키는 즉시 상태가 변경된다고 생각이 들 수도 있다. 하지만 리액트에서는 이런 Mental Model과는 다르게 동작한다.
리액트에서 렌더링은 컴포넌트 함수를 호출하는 것을 의미한다.
컴포넌트 함수가 호출되면 JSX를 리턴하는데 이 리턴된 JSX가 UI 스냅샷이다. Props, 이벤트 핸들러, 지역 변수들이 렌더링 시점의 상태 값을 기반으로 처리된다.
사진이나 영화 장면과 다르게 컴포넌트 함수가 리턴한 UI 스냅샷은 인터랙티브하다. 유저 입력에 반응하는 이벤트 리스너 같은 요소들이 UI 스냅샷에 포함되어 있기 때문이다. 리액트는 이 스냅샷과 웹페이지 화면을 일치시키기 위해 화면을 업데이트하고 이벤트 핸들러들을 연결한다. 이런 과정을 통해 유저가 버튼을 클릭할 경우 이벤트 핸들러가 작동하게 된다.
컴포넌트 함수 호출
-> 스냅샷 처리
-> DOM 트리 업데이트
컴포넌트의 메모리 관점에서, 상태는 함수 리턴후 사라지는 일반적인 자바스크립트 변수같은게 아니다. 리액트에서 상태는 함수 외부인 리액트 그 자체에 존재하고 있다.
리액트가 컴포넌트 함수를 호출할 때, 그 특정한 렌더링 때의 상태를 스냅샷 찍는다.
컴포넌트 함수는 렌더링 시점의 상태 값을 사용하여 새로운 props와 이벤트 핸들러가 적용된 UI 스냅샷을 리턴한다.
setState 함수 호출
-> 상태 업데이트
-> 상태 값의 스냅샷이 컴포넌트로 보내진다.
이게 어떻게 작동하는지 확인하기 위한 코드를 보자.
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>
</>
)
}
버튼 클릭 시 한 번에 이전 상태에 3이 더해진게 나타날거라 생각할 수도 있다. 하지만 코드를 실행해보면 1만 더해지는 것을 알 수 있다.
setNumber
함수가 하는 것은 버튼 클릭이 일어난 시점의 렌더링 단계에서 상태를 변경하는게 아니라 다음 리렌더링때 반영될 상태 값을 업데이트 하는 것이다.
버튼이 클릭된 시점의 렌더링 단계에서는 number 값이 0이기 때문에 이 렌더링 단계에서 아무리 setNumber 함수가 실행되어 상태 값 변경을 시도해도 현재 렌더링 시점의 상태값을 기반으로 +1
된 결과만 다음 렌더링때 반영된다.
<button onClick={() => {
setNumber(number + 1); // 다음 렌더링때 number 값을 1로 만들기 위해 준비.
setNumber(number + 1); // 다음 렌더링때 number 값을 1로 만들기 위해 준비.
setNumber(number + 1); // 다음 렌더링때 number 값을 1로 만들기 위해 준비.
}}>+3</button>
number
상태 변수가 평가된 상황은 아래와 같다.
<button onClick={() => {
setNumber(0 + 1); // 다음 렌더링때 number 값을 1로 만들기 위해 준비.
setNumber(0 + 1); // 다음 렌더링때 number 값을 1로 만들기 위해 준비.
setNumber(0 + 1); // 다음 렌더링때 number 값을 1로 만들기 위해 준비.
}}>+3</button>
유저가 웹페이지에서 버튼을 클릭하면 onClick
이벤트 리스너에 등록된 콜백 함수가 호출될 것이고 이 콜백함수 내부의 코드가 모두 처리되면 리렌더링이 일어나게 된다.
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>
</>
)
}
위 코드는 버튼 클릭 후 3초 뒤에 number
상태 값을 보여주는 alert
팝업이 나타나게 한다.
버튼을 클릭 후 이벤트 핸들러가 호출되어 이벤트 핸들러 내부의 코드가 처리되기 시작한다. 이때 setNumber
함수 호출로 인해 리액트는 다음 렌더링때 업데이트된 상태 값을 반영하기 위한 준비를 한다. 아직 리렌더링이 일어나지 않으므로 number
값은 0
이며 그 상황에서 setTimeout
코드가 처리되는데 이때 리렌더링이 일어나지 않았기에 number
값은 여전히 0
이다.
그래서 버튼을 클릭하면 이벤트 핸들러 코드가 처리되고 리렌더링이 일어나 화면에 5로 바뀐게 반영된다. 그리고 약 3초 뒤에 number
값이 alert
팝업을 통해 출력되는데 이때 값은 0이다.
왜냐하면 alert(number)
코드가 처리되던 시점의 렌더링에서는 number
값이 0
이었기 때문이다. number
값의 변경은 리렌더링후에 반영된다.