state와 useState 훅 기초
컴포넌트 내부 상태와 props의 차이를 구분하고 useState로 값을 갱신해 토글 UI의 재렌더링을 구현합니다.
앞 절에서 우리는 props를 이용하여 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 방법을 배웠습니다.
props는 컴포넌트 외부에서 전달받는 읽기 전용 데이터였죠.
하지만 웹 애플리케이션은 사용자의 상호작용(버튼 클릭, 입력 등)에 따라 동적으로 UI가 변경되어야 하는 경우가 많습니다.
예를 들어, 좋아요 버튼을 누르면 좋아요 수가 증가하거나, 입력 필드에 텍스트를 입력하면 해당 텍스트가 화면에 실시간으로 반영되어야 합니다.
이러한 컴포넌트 내부에서 변경될 수 있는 동적인 데이터를 관리하기 위해 리액트는 state(상태)라는 개념을 제공합니다.
이번 장에서는 리액트의 state가 무엇인지, 왜 중요한지, 그리고 함수형 컴포넌트에서 state를 관리하는 가장 기본적인 방법인 useState 훅(Hook)에 대해 자세히 알아보겠습니다.
state란 무엇인가?
state는 컴포넌트 내부에서 관리되며, 시간이 지남에 따라 변할 수 있는 데이터를 의미합니다.
props가 외부로부터 전달받는 속성이라면, state는 컴포넌트 스스로가 가지고 있는 상태라고 이해할 수 있습니다.
state가 변경되면, 리액트는 해당 state를 사용하는 컴포넌트를 자동으로 재렌더링(re-render)하여 변경된 UI를 화면에 반영합니다.
state의 주요 특징
- 컴포넌트 내부에서 관리:
state는 화면 트리의 해당 컴포넌트 위치에 속합니다. setter를 자식에 전달하거나 자식의 콜백 요청을 받아 부모가 호출할 수도 있으므로, 상태의 소유 위치와 호출 위치를 구별합니다. - 가변적인 데이터: 사용자의 상호작용, 네트워크 응답 등 외부 요인에 의해 값이 변경될 수 있습니다.
state변경 시 재렌더링:state가 변경되면 리액트는 자동으로 해당 컴포넌트를 다시 그리고, 변경된state값을 반영합니다. 이는 선언적 UI의 핵심 원리 중 하나입니다. 개발자가 직접 DOM을 조작할 필요 없이,state만 변경하면 리액트가 알아서 UI를 업데이트해 줍니다.
useState 훅 소개
과거에는 클래스형 컴포넌트에서만 state를 사용할 수 있었습니다.
하지만 리액트 16.8 버전부터 도입된 Hooks(훅스) 덕분에 함수형 컴포넌트에서도 state를 비롯한 다양한 리액트 기능을 사용할 수 있게 되었습니다.
useState는 그중에서도 가장 기본적인 훅이며, 함수형 컴포넌트에서 state를 관리할 수 있도록 해줍니다.
- 최상위에서만 호출: 훅은 리액트 함수 컴포넌트의 최상위(at the top level)에서만 호출해야 합니다. 반복문, 조건문, 중첩된 함수 내에서는 훅을 호출할 수 없습니다.
- 리액트 함수 컴포넌트에서만 호출: 훅은 일반 자바스크립트 함수에서는 호출할 수 없고, 오직 리액트 함수 컴포넌트 또는 커스텀 훅에서만 호출해야 합니다.
useState 같은 Hook의 호출 순서가 렌더마다 같아야 React가 이전 상태와 현재 호출을 대응시킬 수 있습니다. 조건에 따라 버튼을 보여주는 것은 가능하지만, 그 조건 안에서 useState를 호출하지는 않습니다.
useState 훅 사용법 기초
state 업데이트 큐 — 상태 추적
count=0의 한 핸들러에서 교체 세 번은 최종 1, updater 세 번은 0→1→2→3이다. 핸들러 snapshot과 대기 큐/다음 렌더의 다른 값을 추적할 가치가 크다.
STATE SNAPSHOT & QUEUE
Updater는 큐의 직전 결과를 받아 다음 값을 계산합니다
같은 이벤트 핸들러의 count가 0일 때를 비교합니다. setter 호출 뒤 그 핸들러의 count는 계속 0이며, React는 큐를 처리해 다음 렌더의 값을 정합니다.
값 전달 세 번 · 다음 값 1
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);모두 같은 스냅샷 0을 읽어 1을 전달합니다.
큐: 1로 교체 → 1로 교체 → 1로 교체
다음 렌더 count: 1Updater 세 번 · 다음 값 3
setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1);각 함수는 앞선 결과를 이어받아 계산합니다.
큐: 0 → 1 → 2 → 3
다음 렌더 count: 3현재 핸들러와 다음 렌더
setter는 현재 지역 변수를 바꾸지 않습니다. 같은 이벤트의 업데이트를 모아 처리하며, 서로 다른 클릭 이벤트까지 한 큐라고 생각하지 않습니다. Updater는 순수한 함수로 작성합니다.
본문의 ToggleSwitch 두 개
setIsToggled(prev => !prev);
A: false → true
B: false 유지같은 컴포넌트를 두 위치에 사용해도 각 state는 독립적입니다. A의 버튼을 누르면 A의 다음 렌더만 자기 상태를 반전합니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 관계·조건 설명 | 같은 이벤트의 count 0에서 시작하는 서로 독립적인 두 경로는 각각 노드 4개·간선 3개로 구성된다. 왼쪽은 1로 교체 세 번 뒤 1, 오른쪽은 앞선 결과에 1씩 더해 0→1→2→3이다. 두 비교 경로 사이에는 간선이 없다. 현재 핸들러의 count는 두 경우 모두 0으로 유지된다. |
| 전체 관계·조건 설명 | 같은 이벤트의 count 0에서 시작하는 서로 독립적인 두 경로는 각각 노드 4개·간선 3개로 구성된다. 왼쪽은 1로 교체 세 번 뒤 1, 오른쪽은 앞선 결과에 1씩 더해 0→1→2→3이다. 두 비교 경로 사이에는 간선이 없다. 현재 핸들러의 count는 두 경우 모두 0으로 유지된다. 모바일에서도 노드와 연결 방향·독립 경로가 같다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| count0 snapshot | handler count: 0 / 큐: 요청 전 | 한 handler에서 세 요청을 만든다 | handler의 count 변수는 실행 중 유지된다 |
| 세 요청 비교 | 교체 큐: 1,1,1 / updater: 0→1→2→3 | 직접 값은 1/1/1 교체이고 updater는 앞 결과에 +1이다 | 요청 값 계산과 큐 적용을 구분한다 |
| 다음 render | 직접 값: 1 / updater: 3 | 교체 경로는 1, updater 경로는 3을 렌더한다 | 두 독립 비교는 서로 순차 실행되는 큐가 아니다 |
state 업데이트 큐 — 상태 추적
count=0의 한 핸들러에서 교체 세 번은 최종 1, updater 세 번은 0→1→2→3이다. 핸들러 snapshot과 대기 큐/다음 렌더의 다른 값을 추적할 가치가 크다.
STATE SNAPSHOT & QUEUE
Updater는 큐의 직전 결과를 받아 다음 값을 계산합니다
같은 이벤트 핸들러의 count가 0일 때를 비교합니다. setter 호출 뒤 그 핸들러의 count는 계속 0이며, React는 큐를 처리해 다음 렌더의 값을 정합니다.
값 전달 세 번 · 다음 값 1
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);모두 같은 스냅샷 0을 읽어 1을 전달합니다.
큐: 1로 교체 → 1로 교체 → 1로 교체
다음 렌더 count: 1Updater 세 번 · 다음 값 3
setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1);각 함수는 앞선 결과를 이어받아 계산합니다.
큐: 0 → 1 → 2 → 3
다음 렌더 count: 3현재 핸들러와 다음 렌더
setter는 현재 지역 변수를 바꾸지 않습니다. 같은 이벤트의 업데이트를 모아 처리하며, 서로 다른 클릭 이벤트까지 한 큐라고 생각하지 않습니다. Updater는 순수한 함수로 작성합니다.
본문의 ToggleSwitch 두 개
setIsToggled(prev => !prev);
A: false → true
B: false 유지같은 컴포넌트를 두 위치에 사용해도 각 state는 독립적입니다. A의 버튼을 누르면 A의 다음 렌더만 자기 상태를 반전합니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 관계·조건 설명 | 같은 이벤트의 count 0에서 시작하는 서로 독립적인 두 경로는 각각 노드 4개·간선 3개로 구성된다. 왼쪽은 1로 교체 세 번 뒤 1, 오른쪽은 앞선 결과에 1씩 더해 0→1→2→3이다. 두 비교 경로 사이에는 간선이 없다. 현재 핸들러의 count는 두 경우 모두 0으로 유지된다. |
| 전체 관계·조건 설명 | 같은 이벤트의 count 0에서 시작하는 서로 독립적인 두 경로는 각각 노드 4개·간선 3개로 구성된다. 왼쪽은 1로 교체 세 번 뒤 1, 오른쪽은 앞선 결과에 1씩 더해 0→1→2→3이다. 두 비교 경로 사이에는 간선이 없다. 현재 핸들러의 count는 두 경우 모두 0으로 유지된다. 모바일에서도 노드와 연결 방향·독립 경로가 같다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| count0 snapshot | handler count: 0 / 큐: 요청 전 | 한 handler에서 세 요청을 만든다 | handler의 count 변수는 실행 중 유지된다 |
| 세 요청 비교 | 교체 큐: 1,1,1 / updater: 0→1→2→3 | 직접 값은 1/1/1 교체이고 updater는 앞 결과에 +1이다 | 요청 값 계산과 큐 적용을 구분한다 |
| 다음 render | 직접 값: 1 / updater: 3 | 교체 경로는 1, updater 경로는 3을 렌더한다 | 두 독립 비교는 서로 순차 실행되는 큐가 아니다 |
useState 훅은 다음과 같은 형태로 사용합니다.
import { useState } from 'react'; // (1) useState 훅 임포트
function MyCounter() {
// (2) useState 호출: 배열 비구조화 할당으로 state 변수와 setter 함수 얻기
const [count, setCount] = useState(0); // 초기값은 0
return (
<div>
<p>현재 카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>증가</button> {/* (3) setter 함수를 이용해 state 변경 */}
</div>
);
}하나씩 자세히 살펴보겠습니다.
useState 임포트:
useState 훅을 사용하려면 먼저 react 라이브러리에서 이를 불러와야 합니다.
import { useState } from 'react';useState 호출 및 반환값:
useState 함수를 호출하면, 요소 두 개를 가진 배열을 반환합니다.
우리는 이 배열의 요소를 비구조화 할당(Destructuring Assignment)을 통해 추출하여 사용합니다.
const [state변수, state변경함수] = useState(초기값);state변수(예:count): 현재state의 값을 담고 있는 변수입니다. 이 값은 해당 렌더의 스냅샷이며 다음 렌더에서 새 값을 받습니다.state변경함수(예:setCount):state의 값을 업데이트하는 함수입니다. 이 함수를 호출할 때 인자로 새로운state값을 전달합니다.state는 반드시 이 함수를 통해서만 변경해야 합니다. (이 예제의 state 변수는const이므로 직접 다시 대입하면 에러가 납니다. setter는 다음 렌더를 요청합니다.)초기값(예:0): 해당state를 처음 초기화할 때의 값입니다. 다음 렌더의 인자 값이 바뀌어도 그state를 자동으로 재초기화하지 않습니다. 함수 인자를 전달하면 초기화 함수로 호출하는 특별한 규칙이 있으므로, 이 장의 숫자·불리언 값과 구별합니다.
예시에서 const [count, setCount] = useState(0);는 count라는 state 변수를 만들고 초기값을 0으로 설정하며, 이 count를 변경할 때는 setCount 함수를 사용하겠다는 의미입니다.
state 변경하기:
state를 변경하려면 두 번째 반환값인 state변경함수(예: setCount)를 호출해야 합니다.
이 함수에 새로운 state 값을 인자로 전달합니다.
<button onClick={() => setCount(count + 1)}>증가</button>버튼을 클릭하면 setCount 함수가 호출되고, count의 현재 값에 1을 더한 새로운 값을 count state에 할당합니다.
setter는 이 핸들러의 count 변수에 새 값을 즉시 대입하지 않습니다. 현재 값과 다음 값이 같으면 React가 갱신을 생략할 수 있으며, 항상 화면을 다시 그린다고 보장하지 않습니다. useState API의 초기화·setter 규칙을 참고할 수 있습니다.
count state가 변경되었으므로, 리액트는 MyCounter 컴포넌트를 다시 렌더링하고 화면의 p 태그에 업데이트된 count 값을 보여줍니다.
실습 예제: 토글 버튼 만들기
useState 훅을 사용하여 간단한 토글(Toggle) 버튼을 만들어 보겠습니다.
버튼을 누를 때마다 텍스트가 켜짐과 꺼짐 상태로 번갈아 바뀌도록 할 것입니다.
ToggleSwitch.jsx 컴포넌트 생성:
src/components 폴더 안에 ToggleSwitch.jsx 파일을 생성합니다.
import { useState } from 'react';
function ToggleSwitch() {
// isToggled: 현재 상태 값 (초기값: false)
// setIsToggled: isToggled를 변경할 때 사용하는 함수
const [isToggled, setIsToggled] = useState(false);
const handleToggle = () => {
// setIsToggled 함수를 사용하여 isToggled의 현재 값을 반전
setIsToggled(prev => !prev);
};
return (
<div style={{ padding: '20px', border: '1px solid #ccc', borderRadius: '8px', margin: '20px' }}>
<h2>토글 스위치</h2>
<p>
현재 상태: <span style={{ fontWeight: 'bold', color: isToggled ? 'green' : 'red' }}>
{isToggled ? '켜짐' : '꺼짐'}
</span>
</p>
<button
onClick={handleToggle}
style={{
padding: '10px 20px',
fontSize: '16px',
cursor: 'pointer',
backgroundColor: isToggled ? '#4CAF50' : '#f44336', // 상태에 따라 버튼 색상 변경
color: 'white',
border: 'none',
borderRadius: '5px'
}}
>
{isToggled ? '끄기' : '켜기'}
</button>
</div>
);
}
export default ToggleSwitch;useState(false):isToggled라는state를 선언하고 초기값을false로 설정했습니다.handleToggle함수: 버튼 클릭 시 호출될 함수입니다.setIsToggled(prev => !prev)를 통해 직전 상태를 기준으로 값을 반전시켜state를 업데이트합니다.- UI 렌더링:
isToggled값에 따라 텍스트 ('켜짐'/'꺼짐')와 버튼의 배경색이 동적으로 변경되도록 했습니다.
App.jsx에서 ToggleSwitch 컴포넌트 사용:
src/App.jsx 파일을 열고, 기존 내용을 수정하거나 추가하여 ToggleSwitch 컴포넌트를 불러와 사용합니다.
import './App.css';
import UserProfile from './components/UserProfile'; // 기존 UserProfile이 있다면 그대로 둡니다.
import ToggleSwitch from './components/ToggleSwitch'; // ToggleSwitch 컴포넌트 불러오기
function App() {
// ... 기존 UserProfile 관련 코드 ...
return (
<div className="App">
<h1>나의 React 앱</h1>
{/* UserProfile 컴포넌트 (있다면) */}
{/* <UserProfile name="김리액트" age={28} location="서울" /> */}
{/* ToggleSwitch 컴포넌트 추가 */}
<ToggleSwitch />
<ToggleSwitch /> {/* 여러 번 재사용해도 각자 상태를 가집니다! */}
</div>
);
}
export default App;결과 확인: 브라우저를 확인하면 토글 스위치가 렌더링되어 있을 것입니다.
버튼을 클릭할 때마다 텍스트와 버튼의 색상이 바뀌는 것을 확인해 보세요.
특히, ToggleSwitch 컴포넌트를 두 번 사용했지만 각각 독립적으로 상태를 관리하고 있음을 확인할 수 있습니다.
이것이 바로 리액트 state의 강력함입니다!
두 토글의 결과를 확인할 때는 두 위치가 계속 렌더되는 조건을 유지합니다. 새로 연 화면에서는 둘 다 꺼짐 / 켜기이고, 첫 버튼만 한 번 누르면 첫 것은 켜짐 / 끄기, 둘째는 꺼짐 / 켜기를 유지해야 합니다. 다음 확인은 화면이 반영된 뒤 다시 첫 버튼을 누르는 방식으로 합니다. 이 조건이면 첫 것만 원래 표시로 돌아옵니다. 컴포넌트를 화면 트리에서 제거한 뒤 다시 추가한 경우에는 새 초기값으로 시작하므로, 단순 재렌더와 제거 후 재등장을 구별합니다. 상태 유지와 초기화의 공식 설명을 참고할 수 있습니다.
같은 ToggleSwitch 컴포넌트를 여러 번 렌더링해도, 각 인스턴스는 자기만의 state 값을 따로 가지고 독립적으로 업데이트됩니다.
state 업데이트 시 주의사항
-
직접
state를 변경하지 마세요! 이 예제의count는const로 선언했으므로count = count + 1처럼 다시 대입하면 JavaScript의TypeError가 먼저 발생합니다.지역 변수에 값을 대입하는 것은 React에 다음 렌더를 요청하는 일이 아닙니다. 객체·배열을 state로 쓸 때도 기존 내용을 직접 고치지 않고 새 값을 만들어 setter에 전달합니다.
// ❌ 잘못된 예시: state를 직접 변경 const [count, setCount] = useState(0); // count = count + 1; // 이렇게 하면 안 됩니다! // ⭕ 올바른 예시: setter 함수 사용 setCount(count + 1); -
비동기적 업데이트:
useState의setter함수는state를 비동기적으로 업데이트할 수 있습니다.즉, 같은 이벤트 핸들러에서
setCount(count + 1)직후console.log(count)는 그 렌더의 기존 값을 읽습니다.만약 이전
state값에 의존하여 새로운state를 계산해야 한다면,setter함수에 콜백 함수를 전달하는 것이 안전합니다.// 이전 state 값에 기반하여 업데이트할 때 setCount(prevCount => prevCount + 1);prevCount는 React가 업데이트 큐를 처리할 때 앞선 업데이트까지 적용한 값을 받습니다.같은 클릭 핸들러에서 현재 count가 2일 때, 값을 두 번 전달하면 두 호출 모두 현재 스냅샷의 2를 읽어 3으로 교체하도록 요청합니다. Updater를 두 번 전달하면 큐에서 앞 계산 결과를 다음 함수에 넘깁니다. 서로 다른 클릭 두 번은 여기의 한 핸들러 두 호출과 구별합니다.
// 같은 핸들러, 현재 count = 2 setCount(count + 1); setCount(count + 1); // 다음 렌더: 3 // 별도 비교: 역시 현재 count = 2에서 시작 setCount(c => c + 1); setCount(c => c + 1); // 큐 계산 2 → 3 → 4, 다음 렌더: 4도식의 count=0·세 호출 비교도 같은 원리입니다. 함수는 입력받은 값으로 다음 값만 계산하도록 작성합니다. Updater 안에서 알림·네트워크 요청 같은 부수 효과를 실행하지 않습니다. 업데이트 큐의 공식 설명을 확인할 수 있습니다.
이제는 props와 state라는 리액트의 양대 산맥 중 하나인 state의 기초를 다진 셈입니다.
핵심은 간단합니다.
화면에 영향을 주는 값은 일반 변수에 직접 대입하지 말고 state로 두며, 변경은 setter를 통해 다음 렌더를 요청해야 합니다.
혼자 큐와 두 토글을 추적하기
현재 count가 2인 한 핸들러에서 setCount(count + 4) 뒤에 setCount(c => c * 2)를 호출합니다. 바로 이어 console.log(count)로 읽는 값과 다음 렌더의 값을 구별하세요. 별도 상황으로 두 ToggleSwitch가 모두 꺼짐에서 시작하여 첫 스위치를 두 번, 둘째 스위치를 한 번 누릅니다. 클릭마다 다음 화면이 반영된 뒤 다음 클릭을 한다고 둘 때 최종 텍스트와 버튼 문구를 적어 보세요.
큐 처리와 상태 소유 해설
현재 핸들러의 count는 2이므로 로그는 2입니다. 큐는 6으로 교체한 뒤 그 결과에 2를 곱해 다음 렌더는 12입니다. 두 토글은 서로 다른 위치의 상태를 가지므로 첫 것은 꺼짐→켜짐→꺼짐이고 버튼은 켜기, 둘째 것은 꺼짐→켜짐이고 버튼은 끄기입니다. 둘 다 같은 함수 정의를 사용한다는 이유로 상태가 하나로 합쳐지지는 않습니다.
한 이벤트의 계산을 코드로 완성하기
- 아래와 같이 count가 2에서 시작하는 Counter를 만듭니다. 한 번의 클릭에서 updater로 1을 더하고, 현재 스냅샷의 count에 4를 더한 값으로 교체하고, 마지막으로 updater로 2배를 만듭니다. 핸들러 끝의 로그와 다음 화면의 count를 각각 예측한 뒤 완성하세요. 계산 순서를 바꾸지 않으며, 다음 클릭은 화면 반영 후에만 합니다.
- 첫 클릭이 반영된 다음 같은 버튼을 다시 한 번 클릭합니다. 두 번째 핸들러가 읽는 count와 다음 렌더 값을 계산하세요. 이 문제는 같은 핸들러 안의 세 요청과, 별도로 발생한 두 클릭을 구별하는 연습입니다.
- 원 ToggleSwitch의 handleToggle 안에서
setIsToggled(prev => !prev)를 두 번 연속 호출하는 별도 실험을 합니다. false에서 한 번 클릭한 뒤 최종 표시를 예측하세요. 상태를 true로 한 번 바꾸는 실험과 다른 이유, updater 안에 console.log나 알림을 넣어 호출 횟수를 클릭 수로 세면 안 되는 이유도 적습니다. 중간 큐 계산값이 모두 브라우저에 표시된다고 가정하지 않습니다.
완성 Counter와 이벤트별 큐 해설
- 아래를 JSX를 처리하는 학습 프로젝트의
src/App.jsx로 사용합니다. 별도의 서버 요청이나 타이머는 없습니다.
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(2);
function handleClick() {
setCount(c => c + 1);
setCount(count + 4);
setCount(c => c * 2);
console.log(count);
}
return (
<div>
<p>현재 카운트: {count}</p>
<button onClick={handleClick}>세 요청 보내기</button>
</div>
);
}첫 클릭의 로그는 2입니다. 큐는 2 → 3 → 6으로 교체 → 12 순서로 처리되므로 다음 화면은 12입니다. 가운데 요청은 앞 updater의 결과 3에 4를 더하는 계산이 아니라, 핸들러 스냅샷의 2에 4를 더해 미리 계산한 6으로 교체합니다. 업데이트 큐 설명에 값 교체와 updater를 섞는 규칙이 나옵니다.
-
다음 클릭은 새 렌더에서 만든 핸들러를 사용하므로 로그는 12입니다. 큐는
12 → 13 → 16으로 교체 → 32이며 다음 화면은 32입니다. 첫 클릭의 스냅샷 2를 계속 사용하는 것으로 계산하지 않습니다. 상태 스냅샷 설명과 대응합니다. -
한 클릭의 큐는
false → true → false이므로 최종 표시는꺼짐 / 켜기입니다. 중간의 true는 큐 계산값이며, 이 핸들러에서 true 화면과 false 화면이 차례로 모두 그려진다는 보장은 없습니다. updater는 입력으로 받은 상태에서 다음 값만 계산해야 합니다. 개발 StrictMode에서는 순수성 확인을 위해 updater를 추가 호출할 수 있으므로 그 안의 부수 효과 횟수를 사용자 클릭 수로 해석하지 않습니다. useState의 updater 주의사항을 확인합니다.
다음 절부터는 이 상태 값을 여러 컴포넌트와 이벤트 흐름 속에서 더 안전하게 다루는 방법을 살펴봅니다.