state 변수는 읽고 쓸 수 있는 일반 JavaScript 변수처럼 보일 수 있지만, state는 스냅샷처럼 동작한다. 즉, state 변수를 설정해도 이미 가지고 있는 state 변수는 변경되지 않고, 대신 리렌더링이 실행된다.
Setting state triggers renders(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) {
// ...
}
위 예제에서 "send" 버튼을 누르면 다음과 같은 단계를 통해 setIsSent(true)가 React에 UI를 다시 렌더링 하도록 지시하고 있다.
1. onSubmit 이벤트 핸들러가 실행된다.
2. setIsSent(true)가 isSent를 true로 설정하고 새 렌더링을 큐에 대기시킨다.
3. React는 새로운 isSent 값에 따라 컴포넌트를 다시 렌더링한다.
Rendering takes a snapshot in time(렌더링은 그 시점의 스냅샷을 찍습니다)
"렌더링"이란 React가 컴포넌트, 즉 함수를 호출한다는 뜻인데, 해당 함수에서 반환하는 JSX는 시간상의 UI 스냅샷과 같다. prop, 이벤트 핸들러, 로컬 변수 모두 렌더링 당시의 state를 사용해 계산된다.
* React가 컴포넌트를 다시 렌더링 하는 순서
1. React가 함수를 다시 호출한다.
2. 함수가 새로운 JSX 스냅샷을 반환한다.
3. 그러면 React가 반환한 스냅샷과 일치하도록 화면을 업데이트한다.
state는 함수가 반환된 후 사라지는 일반 변수와 달리 React 자체에 존재한다. React가 컴포넌트를 호출하면서 컴포넌트에 특정 렌더링에 대한 state 스냅샷을 제공하고, 컴포넌트는 해당 렌더링의 state 값을 사용해 계산된 새로운 props 세트와 이벤트 핸들러가 포함된 UI 스냅샷을 JSX에 반환한다.
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 버튼을 클릭하면 무슨 일이 일어날까? +3 버튼을 한번 클릭하면 화면에 표시되는 값은 3이 아닌 1이다. 그 이유는 setNumber(number + 1)을 세 번 호출했지만, 이 렌더링에서 number는 이전 스냅샷에 기반해서 0이다. 즉, state를 1로 세 번 설정한 것과 동일한 결과를 나타내는 것이다.
State over time(시간 경과에 따른 state)
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>
</>
)
}
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>
</>
)
}
위 예제코드 모두 버튼을 클릭했을 때 alert 창의 number는 버튼 클릭이벤트가 발생하기 전의 number 값이 표시된다.
그 이유는 React에 저장된 state는 알림이 실행될 때 변경되었을 수 있지만, 사용자가 상호작용한 시점에 state 스냅샷을 사용하는 것은 이미 예약되어 있던 것이기 때문이다. 만약 이벤트 핸들러의 코드가 비동기적이더라도 state의 변수 값은 렌더링 내에서 절대 변경되지 않는다. state 값은 컴포넌트를 호출해 React가 UI의 스냅샷을 찍을 때 고정된 값이기 때문이다.
useState 훅은 상태를 변경하더라도 해당 렌더링에서는 바로 변경된 값을 반영하지 않는다. useState를 호출하여 상태를 변경하면 리액트는 해당 상태를 변경 요청 큐에 저장하고, 현재 렌더링이 끝나고 난 후 다음 렌더링에서 변경된 상태를 적용하기 때문이다. 이렇게 함으로써 여러 상태 변경 요청을 효율적으로 처리하고 불필요한 렌더링을 방지할 수 있다.
따라서 위 예제에서 setNumber(number + 5)가 실행되더라도, 그 즉시 number 변수의 값이 +5가 되는 것이 아니기 때문에 즉시 alert을 호출하더라도 현재 렌더링에서 number 변수의 값은 변경 전의 값인 0이 그대로 남아있다.
만약 변경된 값을 즉시 alert으로 확인하고 싶다면, 아래와 같이 setNumber를 호출한 다음에 alert을 실행하는 로직을 useEffect를 사용하여 렌더링이 완료된 이후에 실행하게 할 수 있다.
import { useState, useEffect } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
useEffect(() => {
alert(number);
}, [number]); // number 상태가 변경될 때마다 useEffect가 실행됨
const handleButtonClick = () => {
setNumber(number + 5);
};
return (
<>
<h1>{number}</h1>
<button onClick={handleButtonClick}>+5</button>
</>
)
}
* 참고 : React 공식문서(https://react-ko.dev/learn)
'이것저것 스터디📚 > React - 공식 문서' 카테고리의 다른 글
React 공식문서 -Updating Objects in State(객체 state 업데이트) (0) | 2023.07.26 |
---|---|
React 공식문서 -Queueing a Series of State Updates(여러 state 업데이트를 큐에 담기) (0) | 2023.07.26 |
React 공식문서 - Render and Commit(렌더링하고 커밋하기) (0) | 2023.07.26 |
React 공식문서 - State: A Component's Memory(컴포넌트의 메모리) (0) | 2023.07.26 |
React 공식문서 - Responding to Events(이벤트에 응답하기) (0) | 2023.07.26 |