state란 useState 사용법 setter로만 비동기·배치 props vs state 📝 문제풀이
◀ 이전 📋 목차 CH 05 ▶
🎛️ CHAPTER 04 · 입문

State — 컴포넌트가 기억하는 값

props가 "부모가 준 값"이었다면, state는 컴포넌트가 스스로 기억하고 바꾸는 값이에요. 이 값이 바뀌면 리액트가 화면을 다시 그려줍니다. 이번 장에서 useState 하나로 그 마법을 익혀요.

🎯 이 장을 끝내면
🧠
state란
컴포넌트가 기억하는 "변하는 값"
이 값이 바뀌면 리액트가 화면을 다시 그려요.

먼저 "상태(state)"가 뭔지 일상으로 감부터 잡을게요. 상태는 그냥 "지금 어떤지"예요 — 그리고 시간이 지나면 바뀌죠.

🚦
신호등
빨강 초록
지금 무슨 색? = 상태
💡
전등
꺼짐 켜짐
지금 켜졌나? = 상태
🛒
장바구니
1개 3개
지금 몇 개? = 상태

리액트 화면도 똑같아요. 사용자가 뭔가 할 때마다 "지금 값"이 바뀌고, 리액트는 그에 맞춰 화면을 다시 그려요. 흐름은 이거예요:

🖱️
사용자 행동
버튼 클릭·글자 입력
🧠
state 값이 바뀜
"지금 값"을 기억
🖥️
화면 자동 갱신
리액트가 다시 그림
🎯 한 컷 정리 🟪 리액트 state = 컴포넌트가 기억하는 "지금 값" (카운터 숫자·입력 글자·on/off 등).
props는 "부모가 준 값(못 바꿈)", state는 "내가 갖고 바꾸는 값". 값이 바뀌면 리액트가 화면을 자동으로 다시 그려요. 🔤 이렇게 화면을 그리는 걸 "렌더링", 바뀌어서 다시 그리는 걸 "리렌더링"이라 불러요.
🆚 일반 지역변수 vs state 일반 지역변수 — 함수(컴포넌트) 안에서 let count = 0처럼 만든 값은, 값을 바꿔도 화면이 다시 그려지지 않고, 컴포넌트가 다시 실행되면 0으로 초기화돼요. 즉 "기억"이 안 돼요.
stateuseState로 만든 값은 리렌더링이 일어나도 값이 유지되고, setter로 바꾸면 리액트가 화면을 다시 그려줘요. 그래서 "화면에 보여야 하고, 바뀌면 화면도 같이 바뀌어야 하는 값"은 지역변수가 아니라 state로 둬야 해요.
// ❌ 일반 지역변수 — 바꿔도 화면이 안 변하고, 다시 실행되면 0으로 돌아감
function Counter() {
  let count = 0;
  return ;
  // 눌러도 화면은 계속 0 — 리렌더가 안 일어나기 때문
}
👀
코드 읽는 법 — onClick={() => ...}"이 버튼을 누르면 화살표 뒤의 코드를 실행해줘"라는 뜻이에요. () =>(화살표 함수)와 이벤트는 CH05에서 제대로 배우니, 지금은 "누르면 실행된다" 정도로만 보면 충분해요.
🧭
한 줄 요약. "화면 = state를 그린 결과." state가 바뀌면 그 함수 컴포넌트가 다시 호출되어 새 화면을 만들고, 리액트가 달라진 부분만 실제 DOM에 반영해요.
🖼️ 그림으로 보기 — setState → 리렌더 사이클
① set함수 호출 "값 바꿔줘" 라고 요청 ② 변경 감지 React가 상태가 바뀐 걸 알아챔 ③ 컴포넌트 재실행 함수 다시 호출 → 새 JSX(화면) 계산 ④ 화면 갱신 바뀐 부분만 실제 DOM에 반영 🔁 state가 또 바뀌면 이 사이클 반복
핵심은 set함수(setter)를 호출해야 이 사이클이 돌아간다는 거예요. count++처럼 값을 직접 바꾸면 ②(변경 감지)가 안 일어나서 화면이 그대로예요. 그리고 리액트는 ④에서 바뀐 부분만 골라 실제 화면(DOM)에 반영해요 — 이렇게 달라진 곳만 찾아 고치는 방식을 가상 DOM(Virtual DOM)이라고 해요(자세힌 뒤 장에서).
🪝
useState 사용법
배열 구조분해로 값과 setter 꺼내기 🟦 순수 JS
훅(Hook) 하나로 state를 만들어요.

함수 컴포넌트에서 state는 useState라는 훅(Hook)으로 만들어요. 리액트가 미리 만들어 둔 특별한 함수예요 — 이름이 전부 use로 시작한다는 게 특징이죠(useState, useEffect…). useState[현재 값, 값을 바꾸는 함수] 두 개가 담긴 배열을 돌려줘요. 이걸 배열 구조분해(const [a, b] = 배열처럼 배열의 0번·1번을 한 번에 꺼내는 문법)로 받습니다.

🔤
잠깐, "setter"가 뭐예요? 어려운 문법이 아니라 역할을 가리키는 별명이에요. useState가 주는 두 번째 값 = "값을 바꾸는(set) 함수", 이걸 setter라고 불러요. 관례상 이름 앞에 set을 붙여요 — setCount, setName처럼요.
자바의 getter/setter를 들어봤다면 발상이 비슷해요: get은 "값을 꺼내는 함수", set은 "값을 넣어주는 함수". 리액트의 setter도 딱 그 "값을 정해주는(set) 함수"예요. 다만 그냥 바꾸는 게 아니라, 바꾸면서 리액트에게 "화면 다시 그려!"라고 알리는 게 리액트 setter만의 특별한 점이에요.
🧩 Hook이란?— 펼쳐 보기
Hook이란?
Hook(훅)은 React가 미리 만들어 둔 특별한 함수들입니다. 함수 컴포넌트 안에서 "React의 기능"을 가져다 쓸 수 있게 해주는 도구예요.
왜 필요한가
원래 함수는 그냥 값을 계산하고 끝나는 거잖아요. 그런데 React 컴포넌트는 화면이 다시 그려질 때마다 "이전 상태를 기억"하거나 "화면이 그려진 뒤에 뭔가를 실행"해야 할 때가 있어요. 일반 JavaScript 함수만으로는 이런 걸 하기 어렵습니다.
Hook은 바로 이런 능력을 함수 컴포넌트에 "연결(hook into)"해주는 역할을 합니다. 그래서 이름도 "hook"(갈고리, 연결고리)이에요.

대표적인 Hook들
Hook하는 일
useState컴포넌트가 값을 "기억"하게 해줌 (state)
useEffect화면이 그려진 후 특정 코드를 실행하게 해줌 (부수 효과)
useContext여러 컴포넌트가 데이터를 공유하게 해줌
이름 규칙
말씀하신 텍스트에 나온 것처럼, Hook 이름은 전부 use로 시작합니다. 이건 React가 강제하는 규칙이자 관례예요.
useState()
useEffect()
useContext()

예시로 감 잡기
function Counter() {
    const [count, setCount] = useState(0); // useState라는 Hook을 "사용"

    return <button onClick={() => setCount(count + 1)}>{count}</button>;
  }
여기서 useState(0)을 호출하는 순간, "이 컴포넌트가 count라는 값을 하나 기억하고 있어야 해"라고 React에게 알려주는 거예요. 이게 바로 Hook이 하는 일입니다.

정리하면: Hook = 함수 컴포넌트가 React의 기능(state, 생명주기 등)을 사용할 수 있게 해주는 특수 함수라고 이해하시면 됩니다.
▲ 접기

import { useState } from 'react';

function Counter() {
  // useState(초기값) → [현재 값, 바꾸는 함수]
  const [count, setCount] = useState(0);
  //      ↑0번        ↑1번         ↑초기값

  return (
    
  );
}
🔎 한 줄씩 뜯어보기 🟪 리액트 useState(0) — 괄호 안 0초기값이에요. 컴포넌트가 처음 그려질 때 딱 한 번만 쓰여요.
count (0번)지금 현재 값. 화면에 그대로 씁니다.
setCount (1번) — 값을 바꾸는 변경 함수(setter). 이걸 호출해야 리렌더가 일어나요. 이름은 자유예요. 관례상 [값, set값] 꼴로 지어요 — [name, setName], [isOpen, setIsOpen]처럼요.
useState 배열 구조분해 [값, setter] 초기값 = 인자 리렌더링 함수형 업데이트
💡
훅 규칙 미리보기. useState 같은 훅은 컴포넌트 함수의 맨 위에서 호출해요. 조건문·반복문 안에서 호출하면 안 돼요. (자세한 규칙은 훅 장에서 다룹니다.)
🚫
setter로만
직접 바꾸지 말고, 반드시 setter로
count++ 는 무시돼요.

state는 절대 직접 바꾸면 안 돼요. count++count = 5처럼 값을 직접 건드리면, 리액트가 변화를 알아채지 못해 화면이 다시 그려지지 않아요. 반드시 변경 함수(setter)를 호출해야 해요.

const [count, setCount] = useState(0);

// ❌ 직접 변경 — 무시됨. 리렌더 안 일어남
count++;
count = count + 1;

// ✅ setter 사용 — 리액트가 값을 바꾸고 화면을 다시 그림
setCount(1);
setCount(count + 1);
❓ 왜 직접 바꾸면 안 되나 🟪 리액트 리액트는 "state가 setter를 통해 바뀌었다"는 신호를 받아야 리렌더링을 예약해요. count++는 그냥 변수값을 조용히 바꾼 것뿐이라 리액트가 알 방법이 없어요. 게다가 다음 리렌더 때 count는 다시 원래 state 값으로 돌아옵니다. 규칙: state를 바꾼다 = setter를 호출한다. 예외는 없어요.
⚠️
객체·배열도 마찬가지. arr.push(x)obj.name = '…'처럼 직접 고치지 말고, 새 배열/객체를 만들어 setter에 넘겨요. (불변성 — 뒤 장에서 자세히 다룹니다.)
비동기·배치
state 변경은 모아서 처리돼요 (배치)
그래서 함수형 업데이트가 필요해요.
😌
먼저 안심하세요. 여기가 이 장에서 제일 어려운 부분이에요. 그런데 실제로 조심할 상황은 딱 하나"이전 값에 이어서 여러 번 바꿀 때"뿐이에요. 그 외엔 지금 몰라도 괜찮으니 부담 없이 읽어보세요.

비동기(asynchronous)란 "요청한 일이 그 즉시가 아니라 조금 이따 처리된다"는 뜻이에요. state가 바로 이래요. setter를 호출해도 그 자리에서 즉시 값이 바뀌는 게 아니에요. 리액트는 한 이벤트에서 일어난 여러 state 변경을 모아서(배치, batching) 한 번에 처리한 뒤 리렌더링해요. 그래서 setter를 부른 직후에 읽은 값은 아직 옛날 값일 수 있어요.

const [count, setCount] = useState(0);

function handleClick() {
  // count 는 지금 0 이라고 가정
  setCount(count + 1); // 0 + 1
  setCount(count + 1); // 여전히 0 + 1 (count는 아직 0)
  setCount(count + 1); // 여전히 0 + 1
  // 결과: 3이 아니라 1! (세 번 다 "0 기준"으로 계산했기 때문)
}
✅ 함수형 업데이트 — 이전 값 기준으로 안전하게 🟦 순수 JS 이전 값을 바탕으로 바꿀 때는 setter에 함수를 넘겨요. 그 함수는 가장 최신의 값을 인자로 받아요. 그래서 여러 번 호출해도 순서대로 누적돼요. setCount(c => c + 1) — "지금 값이 뭐든, 거기에 1 더해줘"라는 뜻. 이렇게 하면 위 코드도 정확히 3이 돼요.
function handleClick() {
  setCount(c => c + 1); // 0 → 1
  setCount(c => c + 1); // 1 → 2
  setCount(c => c + 1); // 2 → 3
  // 결과: 정확히 3 ✅  (앞 setter의 결과를 c로 받기 때문)
}
💡
규칙 하나만. "이전 state를 바탕으로 새 값을 만든다" 싶으면 함수형 업데이트 setCount(c => c + 1)를 써요. 그냥 새 값으로 갈아끼우는 거면 setCount(5)처럼 값을 바로 넘겨도 돼요.
⚖️
props vs state
받은 값이냐, 내가 관리하는 값이냐
헷갈리기 쉬운 둘을 표로 정리해요.

둘 다 "화면을 그리는 데이터"지만 출처와 권한이 달라요. props는 부모가 내려준 읽기 전용 값이고, state는 컴포넌트가 내부에서 직접 관리·변경하는 값이에요.

구분propsstate
누가 주나부모 컴포넌트가 내려줌컴포넌트가 스스로 가짐
바꿀 수 있나❌ 읽기 전용 (못 바꿈)✅ setter로 바꿈
바꾸는 방법부모가 다른 값을 내려줘야 함setState(setter) 호출
만드는 법함수 인자(속성)로 받음useState()로 선언
예시버튼에 넘긴 label, color카운터 숫자, 입력값, 열림/닫힘
🧩 함께 쓰는 그림 🟪 리액트 부모는 자식에게 props로 값을 내려주고, 자식은 필요하면 자기만의 state를 따로 가져요. 예: 부모가 <Toggle label="알림" />로 label(props)을 주면, Toggle 안에서 켜짐/꺼짐은 isOn이라는 state로 관리해요. 외우기: props = 위에서 받은 값(못 바꿈), state = 내가 가진 값(바꿀 수 있음).
🎬
이벤트 발생
버튼 클릭 등
🎛️
setter 호출
state 변경 예약
🔁
리렌더링
컴포넌트 재실행
🖥️
화면 갱신
새 값으로 표시
🧠 이 장 핵심 요약
📝
문제풀이 · 점검
배운 걸 가볍게 점검해봐요
시험이 아니라 "내가 이해했나" 확인용이에요. 틀려도 바로 해설이 나와요.
🧪
문제를 풀면 즉시 정답과 해설이 나오고, 위쪽 바에 점수가 쌓여요. 네 가지 유형(객관식 · O/X · 빈칸 · 개념결과)을 섞어 두었어요.
CH 05 🖱️ 이벤트 처리 — 클릭·입력에 반응하는 화면 만들기