Frontend/Typescript
React + TypeScript: Props, State, Event Handler에 타입 적용하기
미니임
2026. 2. 20. 22:07

React 프로젝트에 TypeScript를 도입하면 컴포넌트 간의 데이터 흐름을 명확하게 정의할 수 있고, 런타임 에러를 사전에 방지할 수 있습니다. 가장 기본이 되는 Props, State, 그리고 Event Handler에 타입을 적용하는 방법을 풍부한 예제와 함께 알아보겠습니다.
1. 컴포넌트 Props에 타입 적용하기
Props는 부모 컴포넌트로부터 전달받는 데이터이므로, 인터페이스(interface)를 사용하여 그 구조를 정의하는 것이 정석입니다.
기초 예제: 기본 Props 정의
interface GreetingProps {
name: string;
age?: number; // 선택적 Props
isLoggedIn: boolean;
}
// React.FC를 사용하는 방식 (최근에는 권장되지 않기도 함)
const Greeting: React.FC<GreetingProps> = ({ name, age, isLoggedIn }) => {
return (
<div>
<h1>안녕하세요, {name}님!</h1>
{isLoggedIn && <p>나이: {age ?? '비공개'}</p>}
</div>
);
};
// 또는 직접 구조 분해 할당에 타입을 지정하는 방식 (현재 권장되는 방식)
function Welcome({ name, age }: GreetingProps) {
return <h1>환영합니다, {name}({age})님!</h1>;
}
고급 예제: children과 스타일 Props
interface ContainerProps {
children: React.ReactNode; // React 요소 전체 (문자열, 컴포넌트 등)
style?: React.CSSProperties; // 인라인 스타일 타입
}
const Card = ({ children, style }: ContainerProps) => {
return <div style={{ border: '1px solid #ddd', ...style }}>{children}</div>;
};
2. useState (State)에 타입 적용하기
useState는 초기값을 통해 타입을 추론하지만, 복잡한 객체나 유니온 타입이 필요한 경우 제네릭(<T>)을 사용해야 합니다.
실무 예제: 단순 타입 vs 복잡한 타입
import { useState } from 'react';
interface User {
id: number;
username: string;
email: string;
}
const UserProfile = () => {
// 1. 초기값으로 타입이 추론되는 경우 (number)
const [count, setCount] = useState(0);
// 2. 유니온 타입이 필요한 경우
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
// 3. 객체 또는 인터페이스가 필요한 경우 (초기값이 null일 수 있음)
const [user, setUser] = useState<User | null>(null);
const login = () => {
setUser({ id: 1, username: 'react_master', email: 'test@example.com' });
};
return (
<div>
<p>현재 상태: {status}</p>
{user ? <p>사용자: {user.username}</p> : <button onClick={login}>로그인</button>}
</div>
);
};
3. 이벤트 핸들러(Event Handler) 타입 정의하기
이벤트 핸들러는 가장 실수가 잦은 부분입니다. React에서 제공하는 합성 이벤트(SyntheticEvent) 타입을 정확히 사용해야 합니다.
실무 예제: 입력 폼과 클릭 이벤트
import React, { useState } from 'react';
const ContactForm = () => {
const [email, setEmail] = useState('');
// 1. Change 이벤트 (Input, Textarea)
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value);
};
// 2. Form Submit 이벤트
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log('전송된 이메일:', email);
};
// 3. Mouse Click 이벤트
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log('버튼 위치:', e.clientX, e.clientY);
};
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={handleChange} />
<button type="submit" onClick={handleClick}>
제출하기
</button>
</form>
);
};
4. useRef와 타이머 타입
DOM 요소에 접근하거나 리렌더링과 무관한 값을 저장할 때 사용하는 useRef 타입 지정법입니다.
실무 예제: 포커스 제어
import { useRef, useEffect } from 'react';
const AutoFocusInput = () => {
// DOM 요소의 경우 HTML 요소를 제네릭으로 전달
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
// optional chaining을 통해 안전하게 접근
inputRef.current?.focus();
}, []);
return <input ref={inputRef} type="text" placeholder="자동 포커스" />;
};
요약
React에서 TypeScript를 효과적으로 활용하기 위한 핵심 요약입니다.
항목타입 지정 방법비고
| Props | interface 정의 후 컴포넌트에 적용 | children은 React.ReactNode |
| useState | useState<Type>(initialValue) | 초기값으로 추론 가능하면 생략 가능 |
| Input Change | React.ChangeEvent<HTMLInputElement> | 요소에 맞는 HTML 타입 지정 필요 |
| Form Submit | React.FormEvent<HTMLFormElement> | e.preventDefault() 사용 시 필수 |
| useRef (DOM) | useRef<HTMLDivElement>(null) | 초기값은 보통 null로 설정 |
TypeScript는 처음에는 번거로워 보일 수 있지만, 컴포넌트가 복잡해질수록 "내가 만든 컴포넌트를 남이(혹은 미래의 내가) 어떻게 사용해야 하는가"를 알려주는 가장 친절한 문서가 됩니다.
반응형