React 독학 가이드 6 - State로 상태 관리하기
React에서 진짜 동적인 앱을 만들려면 State가 필수예요. 버튼 클릭하면 숫자가 바뀌고, 입력하면 화면에 보이고… 이런 게 다 State 덕분이에요.
State가 뭔가요?
State는 컴포넌트 내부에서 변할 수 있는 데이터예요.
일반 변수랑 뭐가 다르냐고요? 일반 변수는 바꿔도 화면이 안 바뀌어요.
// ❌ 이렇게 하면 안 됨
function Counter() {
let count = 0;
const increase = () => {
count = count + 1;
console.log(count); // 1, 2, 3... 찍히긴 함
};
return (
<div>
<p>{count}</p> {/* 계속 0으로 보임! */}
<button onClick={increase}>+</button>
</div>
);
}
변수값이 바뀌어도 React는 “어? 뭐가 바뀌었어?”를 모르거든요. 그래서 화면을 다시 안 그려요.
State를 쓰면:
// ⭕ State 사용
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increase = () => {
setCount(count + 1); // State 업데이트
};
return (
<div>
<p>{count}</p> {/* 1, 2, 3... 화면에 바로 반영! */}
<button onClick={increase}>+</button>
</div>
);
}
실행 결과:
3
버튼 클릭 시 숫자가 증가하며 화면이 업데이트됩니다
State가 바뀌면 React가 컴포넌트를 다시 렌더링해요.
useState 기본 문법
import { useState } from 'react';
const [state, setState] = useState(초기값);
state: 현재 상태 값setState: 상태를 변경하는 함수초기값: 처음 렌더링될 때의 값
다양한 타입의 State
// 숫자
const [count, setCount] = useState(0);
// 문자열
const [name, setName] = useState("");
// 불린
const [isOpen, setIsOpen] = useState(false);
// 배열
const [items, setItems] = useState([]);
// 객체
const [user, setUser] = useState({ name: "", age: 0 });
State 업데이트 방법
1. 직접 값 전달
setCount(10); // count를 10으로
setName("홍길동"); // name을 "홍길동"으로
setIsOpen(true); // isOpen을 true로
2. 이전 값 기반으로 업데이트
// ⚠️ 이렇게도 되긴 하지만...
setCount(count + 1);
// ⭕ 이 방법이 더 안전함
setCount(prev => prev + 1);
왜 함수형 업데이트가 더 안전하냐면, React가 State 업데이트를 비동기로 처리하거든요. 빠르게 여러 번 클릭하면 예상과 다르게 동작할 수 있어요.
// ❌ 문제가 생길 수 있음
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// count가 1만 증가함!
};// ⭕ 의도대로 3 증가
const handleClick = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// count가 3 증가!
};
배열 State 다루기
배열은 직접 수정하면 안 돼요. 새 배열을 만들어야 해요.
항목 추가
const [items, setItems] = useState(["사과", "바나나"]);
// ❌ 직접 수정 (안 됨!)
items.push("오렌지");
setItems(items);
// ⭕ 새 배열 만들기
setItems([...items, "오렌지"]);
항목 삭제
const [todos, setTodos] = useState([
{ id: 1, text: "공부하기" },
{ id: 2, text: "운동하기" },
{ id: 3, text: "청소하기" }
]);
const handleDelete = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
항목 수정
const handleUpdate = (id, newText) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, text: newText } : todo
));
};
객체 State 다루기
객체도 직접 수정하면 안 돼요!
const [user, setUser] = useState({
name: "홍길동",
age: 25,
email: "hong@example.com"
});
// ❌ 직접 수정 (안 됨!)
user.name = "김철수";
setUser(user);
// ⭕ 새 객체 만들기 (spread 사용)
setUser({ ...user, name: "김철수" });
중첩 객체 수정
const [profile, setProfile] = useState({
name: "홍길동",
address: {
city: "서울",
zipCode: "12345"
}
});
// 중첩된 객체 수정
setProfile({
...profile,
address: {
...profile.address,
city: "부산"
}
});
여러 개의 State 사용
State는 여러 개 만들 수 있어요:
function UserForm() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [age, setAge] = useState(0);
return (
<form>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="이름"
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="이메일"
/>
<input
type="number"
value={age}
onChange={(e) => setAge(Number(e.target.value))}
placeholder="나이"
/>
</form>
);
}
또는 객체 하나로 관리
function UserForm() {
const [form, setForm] = useState({
name: "",
email: "",
age: 0
});
const handleChange = (e) => {
const { name, value } = e.target;
setForm({ ...form, [name]: value });
};
return (
<form>
<input name="name" value={form.name} onChange={handleChange} />
<input name="email" value={form.email} onChange={handleChange} />
<input name="age" value={form.age} onChange={handleChange} />
</form>
);
}
관련된 데이터면 객체로 묶는 게 편해요.
실전 예제: 장바구니
import { useState } from 'react';
function ShoppingCart() {
const [cart, setCart] = useState([]);
const products = [
{ id: 1, name: "에어팟", price: 200000 },
{ id: 2, name: "맥북", price: 2000000 },
{ id: 3, name: "아이패드", price: 1000000 }
];
const addToCart = (product) => {
const existing = cart.find(item => item.id === product.id);
if (existing) {
// 이미 있으면 수량 증가
setCart(cart.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
));
} else {
// 없으면 새로 추가
setCart([...cart, { ...product, quantity: 1 }]);
}
};
const removeFromCart = (id) => {
setCart(cart.filter(item => item.id !== id));
};
const total = cart.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
return (
<div>
<h2>상품 목록</h2>
{products.map(product => (
<div key={product.id}>
<span>{product.name} - {product.price.toLocaleString()}원</span>
<button onClick={() => addToCart(product)}>담기</button>
</div>
))}
<h2>장바구니</h2>
{cart.length === 0 ? (
<p>비어있습니다</p>
) : (
<>
{cart.map(item => (
<div key={item.id}>
<span>
{item.name} x {item.quantity} =
{(item.price * item.quantity).toLocaleString()}원
</span>
<button onClick={() => removeFromCart(item.id)}>삭제</button>
</div>
))}
<h3>총액: {total.toLocaleString()}원</h3>
</>
)}
</div>
);
}
실전 예제: 토글 스위치
import { useState } from 'react';
function ToggleSwitch() {
const [isOn, setIsOn] = useState(false);
const toggle = () => setIsOn(prev => !prev);
return (
<button
onClick={toggle}
style={{
padding: '10px 20px',
backgroundColor: isOn ? '#4CAF50' : '#ccc',
color: 'white',
border: 'none',
borderRadius: '20px',
cursor: 'pointer',
transition: 'background-color 0.3s'
}}
>
{isOn ? 'ON' : 'OFF'}
</button>
);
}
실행 결과:
꺼진 상태
켜진 상태
클릭할 때마다 ON/OFF가 토글되며 색상이 변경됩니다
State 사용 시 주의사항
1. 렌더링 중 State 변경 금지
// ❌ 무한 루프 발생!
function BadComponent() {
const [count, setCount] = useState(0);
setCount(count + 1); // 렌더링할 때마다 실행됨!
return <p>{count}</p>;
}
2. 조건문 안에서 useState 사용 금지
// ❌ Hooks 규칙 위반!
function BadComponent({ isLoggedIn }) {
if (isLoggedIn) {
const [user, setUser] = useState(null);
}
// ...
}
// ⭕ 항상 최상위에서 호출
function GoodComponent({ isLoggedIn }) {
const [user, setUser] = useState(null);
if (!isLoggedIn) {
return <p>로그인해주세요</p>;
}
// ...
}
Hooks는 항상 컴포넌트 최상위에서, 같은 순서로 호출해야 해요.
3. State는 비동기로 업데이트됨
const handleClick = () => {
setCount(count + 1);
console.log(count); // 이전 값이 출력됨!
};
State 업데이트 직후에 새 값을 쓰고 싶으면 useEffect를 써야 해요 (다음에 배울 거예요).
다음 단계
State 기초를 배웠으니, 다음 글에서는 State와 Props의 관계를 다뤄볼게요. 둘을 같이 쓰면 컴포넌트 간에 데이터를 주고받으면서 복잡한 앱도 만들 수 있어요.
State 연습을 많이 해보세요. 카운터, 토글, 폼 만들기 같은 간단한 것부터 시작하면 금방 익숙해져요!
React 시리즈 탐색:
← 블로그 목록으로