React 실무 가이드 · Part 11

타입스크립트와 React

타입을 형식적으로 붙이지 않고 실제로 오류를 잡는 데 쓰기

작성 기준2026년 7월버전과 지원 현황은 이후 달라질 수 있으니 공식 문서를 함께 확인하세요.

이 파트에서 다루는 내용

props와 children이벤트와 상태제네릭 컴포넌트any 회피와 서버 응답
01

props 타입이 컴포넌트의 사용 설명서입니다

타입을 붙이면 자동완성이 되고 오타가 잡힙니다. 그보다 중요한 것은 이 컴포넌트에 무엇을 넘겨야 하는지가 코드에 드러난다는 점입니다.

React 전용 타입을 외우기보다 몇 가지 자주 쓰는 형태만 익히면 대부분 해결됩니다.

props 타이핑 기본형tsx
type OrderCardProps = {
  order: Order;
  isSelected?: boolean;            // 선택적 props
  onSelect: (orderId: number) => void;
  children?: React.ReactNode;      // 감싸는 내용
};

function OrderCard({ order, isSelected = false, onSelect, children }: OrderCardProps) {
  return (
    <div className={isSelected ? "card selected" : "card"}>
      <h3>{order.name}</h3>
      <button onClick={() => onSelect(order.id)}>선택</button>
      {children}
    </div>
  );
}

children의 타입은 ReactNode를 씁니다. 문자열, 숫자, 요소, 배열, null을 모두 포함하는 타입입니다.

기본 HTML 속성 확장tsx
// 공용 버튼처럼 기존 태그 속성을 그대로 받고 싶을 때
type ButtonProps = React.ComponentProps<"button"> & {
  variant?: "primary" | "secondary";
};

function Button({ variant = "primary", ...rest }: ButtonProps) {
  return <button className={`btn ${variant}`} {...rest} />;
}

// onClick, disabled, type 등을 별도 선언 없이 그대로 쓸 수 있습니다

공용 UI 컴포넌트를 만들 때 유용합니다. 속성을 하나씩 다시 정의하지 않아도 됩니다.

02

이벤트와 상태 타입은 몇 개만 기억하면 됩니다

자주 쓰는 이벤트 타입
정리
  • 입력 변경: React.ChangeEvent<HTMLInputElement>
  • 폼 제출: React.FormEvent<HTMLFormElement>
  • 클릭: React.MouseEvent<HTMLButtonElement>
  • 키 입력: React.KeyboardEvent<HTMLInputElement>
인라인이면 추론됩니다
실무 팁

JSX 안에 직접 쓴 화살표 함수의 매개변수는 타입을 적지 않아도 추론됩니다. 함수를 밖으로 뺄 때만 명시하면 됩니다.

초기값이 없는 상태
주의

useState(null)은 타입이 null로 고정됩니다. 나중에 값이 들어올 상태는 제네릭으로 명시합니다.

상태와 이벤트 타이핑tsx
// 초기값만으로 추론되는 경우: 그대로 둡니다
const [keyword, setKeyword] = useState("");
const [count, setCount] = useState(0);

// 나중에 값이 들어오는 경우: 제네릭으로 명시합니다
const [order, setOrder] = useState<Order | null>(null);
const [orders, setOrders] = useState<Order[]>([]);

// 함수를 밖으로 뺄 때만 이벤트 타입을 적습니다
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  setKeyword(event.target.value);
};

// null 가능 상태는 사용 전에 확인이 필요합니다
if (!order) return <Empty />;
return <div>{order.name}</div>;
03

제네릭 컴포넌트로 재사용 폭을 넓힙니다

목록, 테이블, 선택 상자처럼 어떤 데이터든 받을 수 있는 컴포넌트가 있습니다. 이때 any를 쓰면 타입 이점이 사라집니다.

제네릭 목록 컴포넌트tsx
type ListProps<T> = {
  items: T[];
  getKey: (item: T) => string | number;
  renderItem: (item: T) => React.ReactNode;
  emptyText?: string;
};

function List<T>({ items, getKey, renderItem, emptyText = "항목이 없습니다" }: ListProps<T>) {
  if (items.length === 0) return <p className="empty">{emptyText}</p>;

  return (
    <ul>
      {items.map((item) => (
        <li key={getKey(item)}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

// 사용하는 쪽에서 타입이 그대로 유지됩니다
<List
  items={orders}
  getKey={(order) => order.id}        // order가 Order로 추론됩니다
  renderItem={(order) => <OrderRow order={order} />}
/>

Part 2에서 다룬 key 규칙을 컴포넌트가 강제하도록 getKey를 필수로 받았습니다. 타입은 이렇게 규칙을 지키게 만드는 데도 쓸 수 있습니다.

04

any는 타입을 끄는 스위치입니다

타입 오류가 났을 때 any를 붙이면 오류는 사라집니다. 다만 그 지점부터 검사가 멈추기 때문에, 실제 문제는 나중에 실행 중에 드러납니다.

any 대신 unknown
안전한 미지정

타입을 모를 때는 unknown을 씁니다. 사용하기 전에 확인을 강제하므로 any보다 안전합니다.

타입 단언 남용
주의

as로 강제 지정하면 컴파일러는 통과하지만 실제 값이 다를 수 있습니다. 서버 응답에 그대로 쓰면 런타임 오류로 이어집니다.

서버 응답 타입
현실적 접근

API 응답 타입을 손으로 적으면 서버가 바뀌었을 때 어긋납니다. OpenAPI 스펙에서 타입을 생성하면 계약과 코드가 함께 움직입니다.

느슨한 타입 주의
설계

status를 string으로 두면 오타가 걸리지 않습니다. 정해진 값이면 유니온 타입으로 좁힙니다.

타입을 좁혀 오류를 잡습니다tsx
// 느슨함: 오타가 컴파일을 통과합니다
type Order = { status: string };
if (order.status === "PAYED") { }      // 오타지만 오류가 없습니다

// 좁힘: 정해진 값만 허용합니다
type OrderStatus = "PAID" | "SHIPPED" | "CANCELED";
type Order = { status: OrderStatus };
if (order.status === "PAYED") { }      // 컴파일 오류로 잡힙니다

Spring Boot 코스 Part 8에서 enum을 문자열로 저장하기로 한 것과 이어집니다. 서버와 화면이 같은 값 집합을 공유하게 됩니다.

실무 기준

타입스크립트를 쓰는 목적은 컴파일을 통과시키는 것이 아니라 실행 전에 오류를 찾는 것입니다. any와 as로 통과시킨 코드는 타입을 쓰지 않은 코드보다 위험합니다. 안전하다고 착각하게 만들기 때문입니다.

버전

버전별 참고

본문은 React 19 기준입니다. 18과 달라지는 부분은 아래에 정리합니다.

  • React 18 이후 children이 props 타입에 자동으로 포함되지 않습니다. 필요하면 직접 선언해야 합니다.
  • React 19에서 ref를 일반 prop으로 받을 수 있게 되면서 전달 방식과 타입 선언이 단순해졌습니다.
  • 타입 정의는 @types/react 버전을 따릅니다. React 버전과 타입 패키지 버전이 어긋나면 설명하기 어려운 오류가 납니다.
체크

이 파트 완료 기준