DEV/React

[React 개발 기초] 7. State

develop-ch94 2026. 8. 10. 17:51
학습 목표: 시간에 따라 변하는 값을 React답게 다루는 법을 익힘

 

(1) State란?

한 줄 정의: 컴포넌트가 기억하는 값. 변하면 화면도 자동 업데이트

 

📺 TV 리모컨 비유

useState는 TV 리모컨에 비유할 수 있음

const [count, setCount] = useState(0);
//     ↑값    ↑변경함수   ↑초기값
  • count = 현재 채널 번호(지금 값)
  • setCount = 채널 바꾸는 버튼(값 바꾸는 도구)
  • useState(0) = 리모컨 초기 설정(처음 값은 0)
중요: 반드시 setCount로 바꿔야 React가 알아차림. count = 5 이렇게 직접 바꾸면 React는 모름

 

🔄 Props vs State

  Props State
누가 관리? 부모 컴포넌트가 전달 자기 자신이 관리
수정 가능? X, 읽기 전용 O, 가능(setter로만)
변하면? 부모가 다시 내려줘야 자동으로 화면 업데이트
비유 받은 택배 내 비밀 노트

(2) useState 사용하기

기본 문법

import { useState } from 'react'; 

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>카운트: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        증가
      </button>
    </div>
  );
}

 

한 줄씩 뜯어보기

const [count, setCount] = useState(0);
부분 의미
useState(0) "0부터 시작하는 state 만들어줘"
[count, setCount] "값은 count로, 변경 함수는 setcount로 받을게"
const 재할당 방지(setCount로만 바꾸도록)

 

작동 원리

1️⃣ 초기 렌더링
   → count = 0, 화면에 0 표시

2️⃣ 사용자가 버튼 클릭
   → setCount(1) 호출
   → React: "오! state가 바뀌었네!"

3️⃣ React가 자동으로 컴포넌트 재실행
   → count = 1, 화면에 1 표시 ✨

(3) 여러 State 함께 쓰기

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  
  function handleSubmit(e) {
    e.preventDefault();
    // 로그인 로직...
    setIsLoggedIn(true);
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button>로그인</button>
    </form>
  );
}

 

분석

<input
  value={email}                               {/* 현재 값 */}
  onChange={(e) => setEmail(e.target.value)}  {/* 바뀔 때 업데이트 */}
/>
  • value={email} : 입력창이 보여줄 값은 email state
  • onChange : 입력이 바뀔 때마다 setEmail 호출
  • e.target.value : 사용자가 방금 입력한 값

(4) 중요한 패턴: 함수형 업데이트

방법 1: 직접 값 전달(기본)

setCount(count + 1);

 

방법 2: 함수 전달(실무에선 이게 더 안전) ⭐

setCount(prev => prev + 1); // prev = 이전 값

 

차이점

function handleClick() {
  // 방법 1: 직접 값
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
  // 1만 증가
  
  // 방법 2: 함수 전달
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
  // 3 증가

 

왜 이렇게 되는 걸까?

방법 1의 함정

// count가 0일 때 버튼 클릭
setCount(count + 1);	// setCount(0 + 1) = 1
setCount(count + 1);	// setCount(0 + 1) = 1 ← 같은 count
setCount(count + 1);	// setCount(0 + 1) = 1 ← 역시 같은 count

// 결국 1로 덮어쓰기만 함
count는 리렌더링 전까진 0으로 고정임. 3번 호출해도 전부 같은 계산을 함

 

방법 2의 똑똑함

setCount(prev => prev + 1);	// prev=0 → 1
setCount(prev => prev + 1);	// prev=1 → 2
setCount(prev => prev + 1);	// prev=2 → 3

 

언제 어떤 걸 써야 하나?

상황 추천
한 이벤트에서 딱 한 번 호출 직접 값 (setCount(count + 1)) OK
여러 번 호출하거나 비동기 작업 후 함수 전달 필수
이전 값에 기반한 업데이트 함수 전달 권장

(5) 토글 패턴

function ToggleButton() {
  const [isOn, setIsOn] = useState(false);
  
  return (
    <button onClick={() => setIsOn(prev => !prev)}>
      {isOn ? 'ON' : 'OFF'}
    </button>
  );
}

 

prev => !prev → "이전 값의 반대"로 바꾸기. 함수형 업데이트 활용의 정석


(6) 자주 하는 안티패턴

❌ 안티패턴 1: State 직접 수정

// ❌ 절대 금지
const [count, setCount] = useState(0);

function handleClick() {
  count = count + 1;	// 직접 수정! React가 모름
  count++;				// 이것도 금지
}

// ✅ 반드시 setter 사용
function handleClick() {
  setCount(count + 1);
}
문제점: React는 setter 호출을  감지해서 리렌더링함. 직접 수정하면 값은 바뀌어도 화면은 그대로임

 

❌ 안티패턴 2: setter 호출 직후 바로 값 읽기

// ❌ 착각의 함정
function handleClick() {
  setCount(count + 1);
  console.log(count);	// 여전히 예전 값
}
이유: State 업데이트는 비동기임. setter를 호출해도 다음 렌더링까진 안바뀜
새 값이 필요하면 함수형 업데이트 내부에서 작업하거나, useEffect를 쓰기

 

❌ 안티패턴 3: 일반 변수로 값 기억하려 하기

// ❌ 리렌더링 때마다 초기화됨
function Counter() {
  let count = 0;	// 일반 변수
  
  return <button onClick={() => count++}>{count}</button>;
}

// ✅ useState 사용
function Counter() {
  const [count, setCount] = useState(0);
  
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
규칙: "시간이 지나면서 변하고, 화면에 영향을 주는 값"은 무조건 useState!

 

❌ 안티패턴 4: Props를 State로 복사

// ❌ 불필요한 중복 관리
function UserCard({ name }) {
  const [userName, setUserName] = useState(name);
  return <h1>{userName}</h2>;
}

// ✅ Props를 그대로 쓰기
function UserCard({ name }) {
  return <h2>{name}</h2>;
}
문제점: Props가 바뀌어도 State는 안 바뀜. 데이터 동기화 버그의 원인