React 이벤트 시스템
React는 브라우저 이벤트를 SyntheticEvent로 감싸 크로스브라우저 일관성을 제공합니다.
// HTML
<button onclick="handleClick()">클릭</button>
// React (camelCase, 함수 참조)
<button onClick={handleClick}>클릭</button>
클릭 이벤트
import { MouseEvent } from "react";
function ButtonExample() {
// 이벤트 객체 없이
function handleSimpleClick() {
console.log("클릭됨");
}
// 이벤트 객체 사용
function handleClick(e: MouseEvent<HTMLButtonElement>) {
console.log("클릭 위치:", e.clientX, e.clientY);
e.preventDefault(); // 기본 동작 방지
}
// 인라인 + 추가 데이터
return (
<div>
<button onClick={handleSimpleClick}>단순 클릭</button>
<button onClick={handleClick}>이벤트 사용</button>
{[1, 2, 3].map(id => (
<button key={id} onClick={() => console.log(id)}>
항목 {id}
</button>
))}
</div>
);
}
입력 이벤트
import { ChangeEvent, useState } from "react";
function InputExample() {
const [value, setValue] = useState("");
function handleChange(e: ChangeEvent<HTMLInputElement>) {
setValue(e.target.value);
}
return (
<div>
<input
type="text"
value={value} // 제어 컴포넌트
onChange={handleChange}
placeholder="입력하세요"
/>
<p>입력값: {value}</p>
<p>글자 수: {value.length}</p>
</div>
);
}
제어 vs 비제어 컴포넌트
// 제어 컴포넌트: React가 상태 관리 (권장)
<input value={value} onChange={e => setValue(e.target.value)} />
// 비제어 컴포넌트: DOM이 상태 관리
const inputRef = useRef<HTMLInputElement>(null);
<input ref={inputRef} defaultValue="초기값" />
// 값 읽기: inputRef.current?.value
폼 처리
import { FormEvent, useState } from "react";
interface LoginForm {
email: string;
password: string;
}
function LoginForm() {
const [form, setForm] = useState<LoginForm>({ email: "", password: "" });
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
function handleChange(e: ChangeEvent<HTMLInputElement>) {
const { name, value } = e.target;
setForm(prev => ({ ...prev, [name]: value }));
}
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault(); // 페이지 새로고침 방지
if (!form.email || !form.password) {
setError("이메일과 비밀번호를 입력하세요.");
return;
}
setIsLoading(true);
setError(null);
try {
await login(form);
alert("로그인 성공!");
} catch (err) {
setError("로그인에 실패했습니다.");
} finally {
setIsLoading(false);
}
}
return (
<form onSubmit={handleSubmit}>
<input
name="email"
type="email"
value={form.email}
onChange={handleChange}
placeholder="이메일"
/>
<input
name="password"
type="password"
value={form.password}
onChange={handleChange}
placeholder="비밀번호"
/>
{error && <p className="error">{error}</p>}
<button type="submit" disabled={isLoading}>
{isLoading ? "로그인 중..." : "로그인"}
</button>
</form>
);
}
키보드 이벤트
import { KeyboardEvent } from "react";
function SearchInput() {
const [query, setQuery] = useState("");
function handleKeyDown(e: KeyboardEvent<HTMLInputElement>) {
if (e.key === "Enter") {
performSearch(query);
}
if (e.key === "Escape") {
setQuery("");
}
}
return (
<input
value={query}
onChange={e => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Enter로 검색"
/>
);
}
주요 이벤트 타입
| 이벤트 | 타입 | 요소 |
|---|---|---|
onClick | MouseEvent<T> | 대부분 |
onChange | ChangeEvent<T> | input, select, textarea |
onSubmit | FormEvent<HTMLFormElement> | form |
onKeyDown | KeyboardEvent<T> | 키보드 입력 가능 요소 |
onFocus | FocusEvent<T> | 포커스 가능 요소 |
onBlur | FocusEvent<T> | 포커스 가능 요소 |
정리
- React 이벤트는 camelCase:
onClick,onChange,onSubmit e.preventDefault()로 기본 동작 방지e.stopPropagation()으로 이벤트 버블링 중단- TypeScript에서 이벤트 타입을 명시하면 자동완성 지원
- 폼은
onSubmit+e.preventDefault()패턴 사용
다음 편에서는 useEffect — 컴포넌트 생명주기와 부수 효과를 처리하는 방법을 배웁니다.