사용자 입력에 반응하고 변하는 데이터를 상태로 관리하기.
이벤트 처리와 state
지금까지는 화면을 "그리기"만 했습니다. 이제 클릭·입력 같은 사용자 행동에 반응하고, 시간에 따라 변하는 데이터를 state(상태) 로 다뤄 봅시다.
학습 목표
onClick등 이벤트 핸들러를 연결할 수 있다.- state 가 무엇이고 왜 props와 다른지 안다.
- state를 불변(immutable) 하게 업데이트할 수 있다.
- 이전 state를 기반으로 안전하게 갱신할 수 있다.
- 객체·배열 state를 올바르게 다룰 수 있다.
이벤트 핸들러 연결하기
JSX에서 이벤트는 카멜케이스 속성에 함수를 넘겨 처리합니다.
function Button() {
function handleClick() {
alert('클릭됨!');
}
return <button onClick={handleClick}>눌러보세요</button>;
}
onClick={handleClick}처럼 함수 자체를 넘깁니다.onClick={handleClick()}는 렌더 시점에 바로 실행돼 버리니 주의하세요.- 인자를 넘기려면 화살표 함수로 감쌉니다:
onClick={() => remove(item.id)}.
| 이벤트 | 속성 |
|---|---|
| 클릭 | onClick |
| 입력 변경 | onChange |
| 폼 제출 | onSubmit |
| 마우스 진입 | onMouseEnter |
| 키 입력 | onKeyDown |
state — 변하는 값 기억하기
화면에 보이면서 시간에 따라 바뀌는 값은 useState 로 관리합니다.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count} 번 클릭
</button>
);
}
useState(초깃값)은[현재값, 변경함수]를 반환합니다.setCount를 호출하면 컴포넌트가 다시 렌더링됩니다.
💡 TIP — props는 부모가 주는 "외부 입력", state는 컴포넌트가 스스로 관리하는 "내부 기억"입니다. 일반 변수에 값을 바꿔도 화면은 갱신되지 않습니다. 반드시 set 함수를 써야 React가 다시 그립니다.
불변하게 업데이트하기
React는 "이전 값과 새 값이 다른가"를 비교해 리렌더를 결정합니다. 그래서 기존 값을 직접 수정하면 안 되고, 새 값을 만들어 교체해야 합니다.
// ❌ 직접 수정 — 화면이 안 바뀜
user.name = '새이름';
setUser(user);
// ✅ 새 객체로 교체
setUser({ ...user, name: '새이름' });
이전 state 기반 업데이트
같은 이벤트에서 state를 여러 번 바꾸거나, 비동기 상황이라면 함수형 업데이트가 안전합니다.
// ❌ 한 클릭에 +1만 됨 (같은 count 값 3번 사용)
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// ✅ 항상 최신 값을 받아 +1 (총 +3)
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
⚠️ 주의 — set 함수는 즉시 반영되지 않습니다. 바로 다음 줄에서
count를 읽으면 아직 옛날 값입니다. 다음 값을 계산할 땐prev => ...형태를 쓰세요.
객체 state
스프레드 연산자로 기존 필드를 복사하고 바꿀 부분만 덮어씁니다.
function Form() {
const [form, setForm] = useState({ name: '', email: '' });
function updateName(e) {
setForm((prev) => ({ ...prev, name: e.target.value }));
}
return <input value={form.name} onChange={updateName} />;
}
배열 state
배열도 마찬가지로 새 배열을 만들어 교체합니다. push, splice 같은 변경 메서드는 피하세요.
function TodoApp() {
const [todos, setTodos] = useState([]);
const add = (text) =>
setTodos((prev) => [...prev, { id: Date.now(), text }]); // 추가
const remove = (id) =>
setTodos((prev) => prev.filter((t) => t.id !== id)); // 삭제
const toggle = (id) =>
setTodos((prev) =>
prev.map((t) => (t.id === id ? { ...t, done: !t.done } : t)) // 수정
);
return (/* ... */);
}
| 동작 | 추천 메서드 |
|---|---|
| 추가 | [...arr, item] |
| 삭제 | arr.filter(...) |
| 수정 | arr.map(...) |
| 정렬 | [...arr].sort(...) (복사 후) |
요약
- 이벤트는
onClick={함수}처럼 함수 자체를 넘긴다. - 변하는 값은
useState로 관리하고, set 함수로만 바꾼다. - state는 불변하게 — 새 객체·배열을 만들어 교체한다.
- 이전 값을 기반으로 바꿀 땐
setX(prev => ...). - 배열은
push대신 스프레드·filter·map을 쓴다.
연습문제
- 버튼을 누를 때마다 1씩 줄어드는 카운터를 만들고, 0 미만으로는 안 내려가게 하세요.
- 한 번의 클릭으로 카운트를 +2 하도록 함수형 업데이트로 작성하세요.
{ name, age }객체 state에서 나이만 1 증가시키는 버튼을 만드세요. (불변 업데이트)- 입력창에 적은 텍스트를 "추가" 버튼으로 배열 state에 넣어 리스트로 보여주세요.
- 4번 리스트의 각 항목 옆에 "삭제" 버튼을 달아
filter로 제거하세요.
힌트 — 1번은
setCount(prev => Math.max(0, prev - 1)). 4번 추가는[...prev, 새항목], 삭제는filter.
💡 연습문제 풀이
불러오는 중…
댓글 0
“React.js” 강좌에 대한 댓글입니다.