2 분 소요

개요

State(상태)는 컴포넌트 내부에서 관리하는 동적 데이터입니다. State가 변경되면 React는 해당 컴포넌트를 자동으로 다시 렌더링합니다. useState Hook을 사용해 함수형 컴포넌트에서 상태를 관리합니다.


기본 사용

import { useState } from "react";

function Counter() {
  //  [현재 상태값,  상태 변경 함수] = useState(초기값)
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>카운트: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(count - 1)}>-1</button>
      <button onClick={() => setCount(0)}>초기화</button>
    </div>
  );
}


State 업데이트 규칙

직접 변경 금지

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

// ❌ 직접 변경 - 리렌더링 안 됨
count = count + 1;

// ✅ setter 함수 사용 - 리렌더링 발생
setCount(count + 1);

이전 상태 기반 업데이트 (함수형 업데이트)

연속 호출이나 비동기 상황에서는 함수형 업데이트를 사용합니다.

// ❌ 연속 호출 시 문제 - count가 이미 고정된 값
const handleClick = () => {
  setCount(count + 1);  // count = 0
  setCount(count + 1);  // count = 0 (여전히!)
  // 결과: 1 (2가 아님)
};

// ✅ 함수형 업데이트 - 항상 최신 상태 기반
const handleClick = () => {
  setCount(prev => prev + 1);  // prev = 0 → 1
  setCount(prev => prev + 1);  // prev = 1 → 2
  // 결과: 2
};


다양한 State 타입

문자열

const [name, setName] = useState("");
const [status, setStatus] = useState<"idle" | "loading" | "error">("idle");

<input
  value={name}
  onChange={(e) => setName(e.target.value)}
/>

불린

const [isVisible, setIsVisible] = useState(false);

<button onClick={() => setIsVisible(!isVisible)}>
  {isVisible ? "숨기기" : "보이기"}
</button>
{isVisible && <p>보이는 내용</p>}

숫자

const [price, setPrice] = useState(10000);

객체

interface User {
  name: string;
  age: number;
  email: string;
}

const [user, setUser] = useState<User>({
  name: "Alice",
  age: 30,
  email: "alice@example.com",
});

// 객체는 반드시 새 객체를 만들어야 함 (스프레드 연산자 활용)
const handleNameChange = (newName: string) => {
  setUser({ ...user, name: newName });   // 나머지는 유지하고 name만 변경
};

// ❌ 직접 수정 - 렌더링 안 됨
// user.name = newName;

배열

const [items, setItems] = useState<string[]>(["사과", "바나나"]);

// 추가
const addItem = (item: string) => {
  setItems([...items, item]);           // 새 배열 생성
};

// 삭제
const removeItem = (index: number) => {
  setItems(items.filter((_, i) => i !== index));
};

// 수정
const updateItem = (index: number, newValue: string) => {
  setItems(items.map((item, i) => (i === index ? newValue : item)));
};


여러 State 관리

연관된 상태는 객체로 묶어서 관리할 수 있습니다.

// 방법 1: 개별 state (단순한 경우 권장)
const [name, setName] = useState("");
const [age, setAge] = useState(0);
const [email, setEmail] = useState("");

// 방법 2: 객체 state (연관된 상태가 많을 때)
const [form, setForm] = useState({
  name: "",
  age: 0,
  email: "",
});

const handleChange = (field: string, value: string | number) => {
  setForm({ ...form, [field]: value });
};


State와 렌더링

State가 변경될 때마다 컴포넌트 함수가 처음부터 다시 실행됩니다.

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

  console.log("렌더링 발생! count:", count);  // 클릭할 때마다 출력

  return (
    <button onClick={() => setCount(count + 1)}>
      클릭 횟수: {count}
    </button>
  );
}

State 변경 → 컴포넌트 리렌더링 → 가상 DOM 비교 → 실제 DOM 최소 업데이트


State 초기화 지연

초기값 계산에 비용이 많이 든다면 함수를 전달해 최초 1회만 실행되게 합니다.

// ❌ 렌더링마다 expensiveCalc() 호출
const [value, setValue] = useState(expensiveCalc());

// ✅ 최초 1회만 호출 (지연 초기화)
const [value, setValue] = useState(() => expensiveCalc());


관련 링크