개발새발

JSX 타입 이해하기 본문

Typescript

JSX 타입 이해하기

비숑주인 2025. 5. 24. 02:15

JSX 타입 이해하기

JSX 문법은 HTML처럼 보이지만, 실제로는 순수 JavaScript 값과 TypeScript 타입으로 환원되는 구조이다. 타입 체계를 정확히 이해하면 strict 모드에서 흔히 마주치는 “속성이 존재하지 않는다” 오류를 미리 피할 수 있다.
이번 글에서는 전역 JSX 네임스페이스에서 사용자 컴포넌트까지, 타입이 어떻게 연결되는지를 단계적으로 살펴보려고 한다. 

 

1. 전역 JSX 네임스페이스

@types/react에는 다음과 같은 선언이 존재한다.

declare global {
  namespace JSX {
    interface Element extends React.ReactElement<any, any> {}
    interface IntrinsicElements {
      form: React.DetailedHTMLProps<
        React.FormHTMLAttributes<HTMLFormElement>,
        HTMLFormElement
      >;
      input: React.DetailedHTMLProps<
        React.InputHTMLAttributes<HTMLInputElement>,
        HTMLInputElement
      >;
      // …
    }
  }
}

 

declare global 내에 정의되었기 때문에 import 없이도 JSX.Element, JSX.IntrinsicElements 등을 자유롭게 사용할 수 있다.
JSX에서 <form>·<input>처럼 문자 그대로 나타나는 태그는 우선 JSX.IntrinsicElements에서 대응 타입을 찾는다.

 

2. JSX.IntrinsicElements → React.DetailedHTMLProps

 

각 태그의 타입은 React.DetailedHTMLProps<E, T>이다. 여기서

  • E: HTMLAttributes<T>를 확장한 태그 전용 속성 집합
  • T: 실제 DOM 인터페이스(HTMLFormElement, HTMLInputElement 등)

DetailedHTMLProps 정의는 아래와 같다.

type DetailedHTMLProps<E extends HTMLAttributes<T>, T> =
  ClassAttributes<T> & E;

 

즉, DOM 공통 속성과 ref, key 같은 React 전용 속성이 교집합으로 합쳐진 구조이다.

3. HTMLAttributes와 ClassAttributes

  • HTMLAttributes<T>: className, style, defaultValue, onClick 등 HTML + React 고유 속성을 묶은 인터페이스
  • ClassAttributes<T>: 모든 요소가 가질 수 있는 key, ref 속성을 보유

DetailedHTMLProps는 두 인터페이스를 합쳐 태그별 전체 prop 집합을 완성한다.


4. 태그 전용 속성 확장

  • FormHTMLAttributes<T>: encType, noValidate, method 등 <form> 전용 속성을 정의
  • InputHTMLAttributes<T>: accept, alt, onChange 등 <input> 전용 속성을 정의

각 인터페이스는 HTMLAttributes<T>를 상속하므로 공통 속성 + 전용 속성을 모두 포함하게 된다.


5. HTMLFormElement와 HTMLInputElement의 출처

두 인터페이스는 TypeScript 기본 라이브러리인 lib.dom.d.ts에 선언되어 있다.
브라우저 런타임 메서드(checkValidity, focus, value 등)는 이곳에 있으며 React 타입과는 별개이다.
React는 이 DOM 인터페이스를 DetailedHTMLProps의 두 번째 제네릭 인수로 전달해 ref나 이벤트 핸들러에서 안전하게 사용하도록 한다.


6. Synthetic Event 체인

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();          // 가능
  e.currentTarget.method;      // currentTarget이 HTMLFormElement로 인식
};

 

FormEvent<T>는 SyntheticEvent<T, Event>의 별칭이다.
SyntheticEvent는 다시 BaseSyntheticEvent<E, C, T>를 확장하며,
두 번째 타입 매개변수 C 가 EventTarget & HTMLFormElement가 되어 currentTarget에 정확한 타입을 부여한다.
ChangeEvent<HTMLInputElement> 역시 동일한 메커니즘으로 동작한다.


7. 컴포넌트 반환 타입: JSX.Element, ReactElement, ReactNode

const WordRelay = () => <div>Hello</div>;

 

위 함수의 반환 타입은 () => JSX.Element로 추론된다.
JSX.Element는 ReactElement<any, any>의 별칭에 불과하다.
그러나 children prop에는 () => JSX.Element 대신 ReactNode 를 사용해야 한다.

type ReactNode =
  | ReactElement
  | string
  | number
  | boolean
  | null
  | undefined
  | Iterable<ReactNode>;

 

ReactNode가 폭넓은 유니언 타입이므로 문자열·숫자·배열 등 다양한 값을 children으로 허용할 수 있다.


8. 사용자 컴포넌트 타입 지정: FC/FunctionComponent

interface Props {
  children: ReactNode;
  onSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
}

const Form: React.FC<Props> = ({ children, onSubmit }) => (
  <form onSubmit={onSubmit}>{children}</form>
);

 

React.FC(혹은 FunctionComponent)는

  • props: P
  • context?: any
  • 반환값: ReactElement | null

을 만족하는 함수 타입이며, defaultProps, displayName 등의 정적 필드를 포함한다.


정리

  1. 내장 태그는 JSX.IntrinsicElements에서 타입을 찾는다.
  2. DOM 인터페이스(HTMLInputElement 등)는 TypeScript 표준 라이브러리에 정의되어 있다.
  3. SyntheticEvent는 DOM 요소 타입을 currentTarget에 안전하게 전달한다.
  4. children은 항상 ReactNode 타입이어야 한다.
  5. 함수 컴포넌트는 React.FC<Props> 타입으로 선언하는 것이 권장된다.

'Typescript' 카테고리의 다른 글

js 파일 생성하기  (0) 2025.05.30
React 직접 타이핑 하기  (0) 2025.05.30
React Hooks 분석하기  (0) 2025.05.24
React 타입 분석하기  (1) 2025.05.24
axios의 타입을 어떻게 찾았는지 이해하기  (0) 2025.05.17