2026.07.08

[TypeScript 기초] 리액트에서 타입스크립트 쓰기

리액트에서 TypeScript를 활용해 props, state, 이벤트, API 응답 타입을 안전하게 다루는 방법을 정리합니다.

API타입
props타입
react
react개발
typescript
useState타입
개발기초
리액트
이벤트핸들러
정적타입
타입스크립트
프론트엔드개발
[TypeScript 기초] 리액트에서  타입스크립트 쓰기

리액트에서 타입스크립트 제대로 쓰기

타입스크립트를 리액트에 적용하면 props 오타, undefined 접근, 잘못된 함수 인자 같은 실수를 빌드 단계에서 미리 잡을 수 있어요. 이번 수업에서는 실무에서 가장 자주 마주치는 패턴을 정리합니다.


01. Props 타입 정의

컴포넌트가 받는 props를 interface로 정의하는 게 기본입니다.

tsx
interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;
  variant?: "primary" | "outline" | "ghost";
}
 
export function Button({
  label,
  onClick,
  disabled = false,
  variant = "primary",
}: ButtonProps) {
  return (
    <button onClick={onClick} disabled={disabled} className={variant}>
      {label}
    </button>
  );
}

Tip

?를 붙이면 선택 prop이 됩니다. 기본값은 구조 분해 할당에서 = 값으로 설정하면 가장 깔끔합니다.


02. children 타입

children을 받는 컴포넌트는 ReactNode 타입을 씁니다.

tsx
import type { ReactNode } from "react";
 
interface CardProps {
  title: string;
  children: ReactNode;
}
 
export function Card({ title, children }: CardProps) {
  return (
    <div className="card">
      <h3>{title}</h3>
      <div>{children}</div>
    </div>
  );
}

Info

ReactNode는 문자열, 숫자, JSX, 배열, null 등 리액트가 렌더링할 수 있는 모든 것을 포함합니다. 가장 넓은 범위의 children 타입이에요.


03. useState 타입

초기값이 명확하면 자동 추론되지만, null이거나 빈 배열일 때는 직접 명시해야 합니다.

tsx
// 자동 추론 — 초기값에서 타입을 알 수 있을 때
const [count, setCount] = useState(0); // number 추론
 
// 직접 명시 — null 가능성이 있을 때
const [user, setUser] = useState<User | null>(null);
 
// 직접 명시 — 빈 배열은 타입을 알 수 없어서 반드시 명시
const [list, setList] = useState<string[]>([]);

04. 이벤트 핸들러 타입

이벤트 핸들러를 함수로 분리하면 타입을 명시해야 합니다.

tsx
import type { ChangeEvent, FormEvent } from "react";
 
function LoginForm() {
  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };
 
  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
  };
 
  return (
    <form onSubmit={handleSubmit}>
      <input type="text" onChange={handleChange} />
      <button type="submit">로그인</button>
    </form>
  );
}

자주 하는 실수

e: Event로 쓰면 e.target.value에서 타입 오류가 납니다. 반드시 ChangeEvent<HTMLInputElement>처럼 구체적인 타입을 써야 합니다.


05. API 응답 타입 정의

서버에서 받아오는 데이터는 타입을 직접 정의해서 씁니다.

tsx
interface Post {
  id: number;
  title: string;
  author: {
    name: string;
    email: string;
  };
  tags: string[];
  publishedAt: string | null;
}
 
async function fetchPost(id: number): Promise<Post> {
  const res = await fetch(`/api/posts/${id}`);
  if (!res.ok) throw new Error("포스트를 불러올 수 없습니다.");
  return res.json() as Promise<Post>;
}

중요

res.json()은 기본적으로 any를 반환합니다. as Promise<Post>로 캐스팅하거나, zod 같은 라이브러리로 런타임 검증까지 하는 것이 안전합니다.


정리

타입스크립트는 처음엔 번거롭게 느껴지지만, 컴포넌트가 많아질수록 진가를 발휘합니다. props 타입만 잘 정의해도 팀원이 어떤 값을 넣어야 하는지 자동완성으로 바로 알 수 있거든요.