State란?
3. State란?
State는 컴포넌트의 상태를 나타내는 데이터입니다. Props와 달리 컴포넌트 내부에서 관리되며, 변경 가능하고 변경될 때마다 컴포넌트가 다시 렌더링됩니다.
3.1 Props vs State
Props (속성)
- 부모 컴포넌트에서 전달받는 데이터
- 읽기 전용 (변경 불가)
- 컴포넌트 외부에서 제어
State (상태)
- 컴포넌트 내부에서 관리하는 데이터
- 변경 가능
- 컴포넌트 내부에서 제어
- 변경 시 컴포넌트 재렌더링
3.2 State가 필요한 경우
// State 없이 버튼 클릭 처리 (작동하지 않음)
function Counter() {
let count = 0; // 일반 변수
const handleClick = () => {
count = count + 1; // 값은 변경되지만
console.log(count); // 콘솔에는 찍히지만
// 화면은 업데이트되지 않음!
};
return (
<div>
<h2>카운트: {count}</h2>
<button onClick={handleClick}>+1</button>
</div>
);
}// State 없이 버튼 클릭 처리 (작동하지 않음)
function Counter() {
let count = 0; // 일반 변수
const handleClick = () => {
count = count + 1; // 값은 변경되지만
console.log(count); // 콘솔에는 찍히지만
// 화면은 업데이트되지 않음!
};
return (
<div>
<h2>카운트: {count}</h2>
<button onClick={handleClick}>+1</button>
</div>
);
}위의 예제에서 버튼을 클릭하면 콘솔에는 증가된 값이 출력되지만, 화면의 숫자는 변하지 않습니다. React는 일반 변수의 변경을 감지하지 못하기 때문입니다.
3.3 useState Hook 소개
React에서는 useState Hook을 사용해서 State를 관리합니다.
import { useState } from 'react';
function Counter() {
// useState(초기값) -> [현재값, 값을변경하는함수]
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1); // State 값 변경
};
return (
<div>
<h2>카운트: {count}</h2>
<button onClick={handleClick}>+1</button>
</div>
);
}import { useState } from 'react';
function Counter() {
// useState(초기값) -> [현재값, 값을변경하는함수]
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1); // State 값 변경
};
return (
<div>
<h2>카운트: {count}</h2>
<button onClick={handleClick}>+1</button>
</div>
);
}useState의 구조
const [상태값, 상태변경함수] = useState(초기값);const [상태값, 상태변경함수] = useState(초기값);- 상태값: 현재 상태 데이터
- 상태변경함수: 상태를 변경할 때 사용하는 함수
- 초기값: 컴포넌트가 처음 렌더링될 때의 상태값
3.4 State 변경 시 주의사항
1. 직접 수정 금지
// ❌ 잘못된 방법 - 직접 수정
const [count, setCount] = useState(0);
count = count + 1; // 이렇게 하면 안됩니다!
// ✅ 올바른 방법 - setter 함수 사용
setCount(count + 1);// ❌ 잘못된 방법 - 직접 수정
const [count, setCount] = useState(0);
count = count + 1; // 이렇게 하면 안됩니다!
// ✅ 올바른 방법 - setter 함수 사용
setCount(count + 1);2. 비동기 특성 이해
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
console.log(count); // 아직 이전 값이 출력됨
};
// State 변경 후의 값을 확인하려면 useEffect 사용
// (나중에 배울 예정)
}function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
console.log(count); // 아직 이전 값이 출력됨
};
// State 변경 후의 값을 확인하려면 useEffect 사용
// (나중에 배울 예정)
}3.5 다양한 타입의 State
1. 문자열 State
function NameInput() {
const [name, setName] = useState('');
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<p>안녕하세요, {name}님!</p>
</div>
);
}function NameInput() {
const [name, setName] = useState('');
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<p>안녕하세요, {name}님!</p>
</div>
);
}2. 불린 State
function ToggleButton() {
const [isOn, setIsOn] = useState(false);
return (
<div>
<button
onClick={() => setIsOn(!isOn)}
>
{isOn ? 'ON' : 'OFF'}
</button>
<p>상태: {isOn ? '켜짐' : '꺼짐'}</p>
</div>
);
}function ToggleButton() {
const [isOn, setIsOn] = useState(false);
return (
<div>
<button
onClick={() => setIsOn(!isOn)}
>
{isOn ? 'ON' : 'OFF'}
</button>
<p>상태: {isOn ? '켜짐' : '꺼짐'}</p>
</div>
);
}3. 배열 State
function TodoList() {
const [todos, setTodos] = useState(['React 공부하기', 'JSX 이해하기']);
const addTodo = () => {
const newTodo = `할 일 ${todos.length + 1}`;
setTodos([...todos, newTodo]); // 스프레드 연산자로 새 배열 생성
};
const removeTodo = (index) => {
setTodos(todos.filter((_, i) => i !== index));
};
return (
<div>
<button onClick={addTodo}>할 일 추가</button>
<ul>
{todos.map((todo, index) => (
<li key={index}>
{todo}
<button onClick={() => removeTodo(index)}>삭제</button>
</li>
))}
</ul>
</div>
);
}function TodoList() {
const [todos, setTodos] = useState(['React 공부하기', 'JSX 이해하기']);
const addTodo = () => {
const newTodo = `할 일 ${todos.length + 1}`;
setTodos([...todos, newTodo]); // 스프레드 연산자로 새 배열 생성
};
const removeTodo = (index) => {
setTodos(todos.filter((_, i) => i !== index));
};
return (
<div>
<button onClick={addTodo}>할 일 추가</button>
<ul>
{todos.map((todo, index) => (
<li key={index}>
{todo}
<button onClick={() => removeTodo(index)}>삭제</button>
</li>
))}
</ul>
</div>
);
}4. 객체 State
function UserProfile() {
const [user, setUser] = useState({
name: '',
email: '',
age: 0
});
const updateName = (newName) => {
setUser({
...user, // 기존 객체 복사
name: newName // 특정 속성만 업데이트
});
};
const updateEmail = (newEmail) => {
setUser(prevUser => ({
...prevUser,
email: newEmail
}));
};
return (
<div>
<input
type="text"
placeholder="이름"
value={user.name}
onChange={(e) => updateName(e.target.value)}
/>
<input
type="email"
placeholder="이메일"
value={user.email}
onChange={(e) => updateEmail(e.target.value)}
/>
<div>
<h3>사용자 정보</h3>
<p>이름: {user.name}</p>
<p>이메일: {user.email}</p>
</div>
</div>
);
}function UserProfile() {
const [user, setUser] = useState({
name: '',
email: '',
age: 0
});
const updateName = (newName) => {
setUser({
...user, // 기존 객체 복사
name: newName // 특정 속성만 업데이트
});
};
const updateEmail = (newEmail) => {
setUser(prevUser => ({
...prevUser,
email: newEmail
}));
};
return (
<div>
<input
type="text"
placeholder="이름"
value={user.name}
onChange={(e) => updateName(e.target.value)}
/>
<input
type="email"
placeholder="이메일"
value={user.email}
onChange={(e) => updateEmail(e.target.value)}
/>
<div>
<h3>사용자 정보</h3>
<p>이름: {user.name}</p>
<p>이메일: {user.email}</p>
</div>
</div>
);
}3.6 State 업데이트 최적화
함수형 업데이트
이전 상태값을 기반으로 새로운 값을 계산할 때 사용합니다:
function Counter() {
const [count, setCount] = useState(0);
// 일반적인 방법
const increment = () => {
setCount(count + 1);
};
// 함수형 업데이트 (권장)
const incrementBetter = () => {
setCount(prevCount => prevCount + 1);
};
// 여러 번 클릭할 때의 차이
const incrementMultiple = () => {
// 이렇게 하면 1만 증가 (count 값이 같기 때문)
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
const incrementMultipleBetter = () => {
// 이렇게 하면 3 증가 (이전 값을 기반으로)
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
return (
<div>
<h2>카운트: {count}</h2>
<button onClick={increment}>+1</button>
<button onClick={incrementBetter}>+1 (Better)</button>
<button onClick={incrementMultiple}>+3 (문제 있음)</button>
<button onClick={incrementMultipleBetter}>+3 (올바름)</button>
</div>
);
}function Counter() {
const [count, setCount] = useState(0);
// 일반적인 방법
const increment = () => {
setCount(count + 1);
};
// 함수형 업데이트 (권장)
const incrementBetter = () => {
setCount(prevCount => prevCount + 1);
};
// 여러 번 클릭할 때의 차이
const incrementMultiple = () => {
// 이렇게 하면 1만 증가 (count 값이 같기 때문)
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
const incrementMultipleBetter = () => {
// 이렇게 하면 3 증가 (이전 값을 기반으로)
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
return (
<div>
<h2>카운트: {count}</h2>
<button onClick={increment}>+1</button>
<button onClick={incrementBetter}>+1 (Better)</button>
<button onClick={incrementMultiple}>+3 (문제 있음)</button>
<button onClick={incrementMultipleBetter}>+3 (올바름)</button>
</div>
);
}3.7 실습: 간단한 쇼핑 카트
State를 활용한 쇼핑 카트를 만들어봅시다:
import { useState } from 'react';
function ShoppingCart() {
const [items, setItems] = useState([]);
const [inputValue, setInputValue] = useState('');
const addItem = () => {
if (inputValue.trim()) {
const newItem = {
id: Date.now(),
name: inputValue.trim(),
quantity: 1,
price: Math.floor(Math.random() * 20000) + 1000
};
setItems([...items, newItem]);
setInputValue('');
}
};
const removeItem = (id) => {
setItems(items.filter(item => item.id !== id));
};
const updateQuantity = (id, newQuantity) => {
if (newQuantity <= 0) {
removeItem(id);
return;
}
setItems(items.map(item =>
item.id === id ? { ...item, quantity: newQuantity } : item
));
};
const getTotalPrice = () => {
return items.reduce((total, item) => total + (item.price * item.quantity), 0);
};
return (
<div>
<h1>쇼핑 카트</h1>
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="상품명을 입력하세요"
onKeyPress={(e) => e.key === 'Enter' && addItem()}
/>
<button onClick={addItem}>
추가
</button>
</div>
{items.length === 0 ? (
<p>장바구니가 비어있습니다.</p>
) : (
<>
<div>
{items.map(item => (
<div key={item.id}>
<div>
<strong>{item.name}</strong>
<br />
<small>{item.price.toLocaleString()}원</small>
</div>
<div>
<button
onClick={() => updateQuantity(item.id, item.quantity - 1)}
>
-
</button>
<span>{item.quantity}</span>
<button
onClick={() => updateQuantity(item.id, item.quantity + 1)}
>
+
</button>
<button
onClick={() => removeItem(item.id)}
>
삭제
</button>
</div>
</div>
))}
</div>
<div>
<h3>총 금액: {getTotalPrice().toLocaleString()}원</h3>
<button>
결제하기
</button>
</div>
</>
)}
</div>
);
}
export default ShoppingCart;import { useState } from 'react';
function ShoppingCart() {
const [items, setItems] = useState([]);
const [inputValue, setInputValue] = useState('');
const addItem = () => {
if (inputValue.trim()) {
const newItem = {
id: Date.now(),
name: inputValue.trim(),
quantity: 1,
price: Math.floor(Math.random() * 20000) + 1000
};
setItems([...items, newItem]);
setInputValue('');
}
};
const removeItem = (id) => {
setItems(items.filter(item => item.id !== id));
};
const updateQuantity = (id, newQuantity) => {
if (newQuantity <= 0) {
removeItem(id);
return;
}
setItems(items.map(item =>
item.id === id ? { ...item, quantity: newQuantity } : item
));
};
const getTotalPrice = () => {
return items.reduce((total, item) => total + (item.price * item.quantity), 0);
};
return (
<div>
<h1>쇼핑 카트</h1>
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="상품명을 입력하세요"
onKeyPress={(e) => e.key === 'Enter' && addItem()}
/>
<button onClick={addItem}>
추가
</button>
</div>
{items.length === 0 ? (
<p>장바구니가 비어있습니다.</p>
) : (
<>
<div>
{items.map(item => (
<div key={item.id}>
<div>
<strong>{item.name}</strong>
<br />
<small>{item.price.toLocaleString()}원</small>
</div>
<div>
<button
onClick={() => updateQuantity(item.id, item.quantity - 1)}
>
-
</button>
<span>{item.quantity}</span>
<button
onClick={() => updateQuantity(item.id, item.quantity + 1)}
>
+
</button>
<button
onClick={() => removeItem(item.id)}
>
삭제
</button>
</div>
</div>
))}
</div>
<div>
<h3>총 금액: {getTotalPrice().toLocaleString()}원</h3>
<button>
결제하기
</button>
</div>
</>
)}
</div>
);
}
export default ShoppingCart;3.8 정리
State의 핵심 개념을 배웠습니다:
- State는 컴포넌트의 변경 가능한 데이터입니다
- useState Hook을 사용해서 State를 관리합니다
- State 변경은 setter 함수로만 가능합니다
- 다양한 데이터 타입을 State로 관리할 수 있습니다
- 함수형 업데이트로 더 안전하게 State를 변경할 수 있습니다
다음 장에서는 useState Hook에 대해 더 자세히 알아보겠습니다.