[React] State (useState)
개요
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());