State는 컴포넌트의 데이터 저장소다. 값이 변경되면 화면이 자동으로 다시 그려진다.
import { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
return (
<div>
<p>횟수: {count}</p>
<button onClick={() => setCount(count + 1)}>증가</button>
</div>
);
}
useState는 두 개의 값을 배열로 반환한다.
const [value, setValue] = useState(초기값);
function Counter() {
const [num, setNum] = useState(0);
const plus = () => setNum(num + 1);
const minus = () => setNum(num - 1);
const reset = () => setNum(0);
return (
<div>
<h2>{num}</h2>
<button onClick={plus}>+</button>
<button onClick={minus}>-</button>
<button onClick={reset}>초기화</button>
</div>
);
}
하나의 컴포넌트에서 여러 state를 만들 수 있다.
function Form() {
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [email, setEmail] = useState('');
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="이름"
/>
<input
value={age}
onChange={(e) => setAge(e.target.value)}
placeholder="나이"
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="이메일"
/>
<p>이름: {name}, 나이: {age}, 이메일: {email}</p>
</div>
);
}
여러 값을 하나의 객체로 묶어 관리할 수 있다.
function UserForm() {
const [user, setUser] = useState({
name: '',
age: 0,
email: ''
});
const change = (e) => {
setUser({
...user,
[e.target.name]: e.target.value
});
};
return (
<div>
<input name="name" value={user.name} onChange={change} />
<input name="age" value={user.age} onChange={change} />
<input name="email" value={user.email} onChange={change} />
</div>
);
}
function List() {
const [items, setItems] = useState([]);
const [text, setText] = useState('');
const add = () => {
setItems([...items, text]);
setText('');
};
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={add}>추가</button>
<ul>
{items.map((item, i) => <li key={i}>{item}</li>)}
</ul>
</div>
);
}
function TodoList() {
const [todos, setTodos] = useState(['밥먹기', '코딩하기']);
const remove = (index) => {
setTodos(todos.filter((_, i) => i !== index));
};
return (
<ul>
{todos.map((todo, i) => (
<li key={i}>
{todo}
<button onClick={() => remove(i)}>삭제</button>
</li>
))}
</ul>
);
}
// ❌ 잘못된 방법
count = count + 1;
user.name = '철수';
items.push('새 항목');
// ✅ 올바른 방법
setCount(count + 1);
setUser({...user, name: '철수'});
setItems([...items, '새 항목']);
연속으로 state를 변경할 때는 함수형 업데이트를 사용한다.
// ❌ 동작 안 함
setCount(count + 1);
setCount(count + 1);
// count는 1만 증가
// ✅ 올바른 방법
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// count가 2 증가
import { useState } from 'react';
function TodoApp() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const add = () => {
if (input.trim()) {
setTodos([...todos, { id: Date.now(), text: input, done: false }]);
setInput('');
}
};
const toggle = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? {...todo, done: !todo.done} : todo
));
};
const remove = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div>
<h1>할 일 목록</h1>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && add()}
/>
<button onClick={add}>추가</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggle(todo.id)}
/>
<span style={{textDecoration: todo.done ? 'line-through' : 'none'}}>
{todo.text}
</span>
<button onClick={() => remove(todo.id)}>삭제</button>
</li>
))}
</ul>
</div>
);
}
export default TodoApp;
useEffect는 컴포넌트가 화면에 나타날 때, 사라질 때, 값이 변할 때 특정 작업을 실행한다.
import { useEffect, useState } from 'react';
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('화면이 그려짐');
}, [count]);
return (
<div>
<p>횟수: {count}</p>
<button onClick={() => setCount(count + 1)}>증가</button>
</div>
);
}
의존성 배열은 useEffect가 언제 실행될지 결정한다.
// 1. 의존성 배열 없음 - 매번 렌더링될 때마다 실행
useEffect(() => {
console.log('매번 실행');
});
// 2. 빈 배열 - 처음 한 번만 실행 (마운트될 때)
useEffect(() => {
console.log('한 번만 실행');
}, []);
// 3. 값이 들어있음 - 해당 값이 변할 때만 실행
useEffect(() => {
console.log('count가 변할 때만 실행');
}, [count]);
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
// 페이지 로드할 때 한 번만 실행
fetch('/api/users')
.then(res => res.json())
.then(data => setUsers(data));
}, []); // 빈 배열: 처음 한 번만
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
컴포넌트가 사라질 때 정리 작업을 수행한다.
function Timer() {
const [sec, setSec] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setSec(prev => prev + 1);
}, 1000);
// 컴포넌트가 사라질 때 타이머 멈춤
return () => clearInterval(timer);
}, []);
return <p>경과: {sec}초</p>;
}
useState로 state를 만든다useEffect로 특정 시점에 작업을 실행한다