빠른 흐름
type ButtonProps = {
label: string;
disabled?: boolean;
onClick(): void;
};
function Button(props: ButtonProps) {
return <button disabled={props.disabled}>{props.label}</button>;
}
<Button label="Save" onClick={() => {}} />;| 대상 | 타입이 오는 곳 |
|---|---|
<div /> 같은 intrinsic element | JSX.IntrinsicElements |
| 함수 컴포넌트 | 함수 첫 매개변수 |
| class 컴포넌트 | props 관련 클래스 타입 |
| children | props의 children 위치 |
타입 경계
TSX는 JSX 문법과 TypeScript 타입 검사를 함께 적용한다
.tsx 파일에서는 JSX를 JavaScript로 변환하면서 동시에 JSX 요소의 속성을 타입 검사합니다. <Button label="Save" />는 단순 문자열 태그가 아니라 Button 값에 대한 호출 형태로 해석되고, 첫 매개변수 타입이 props 계약이 됩니다.
type UserCardProps = {
id: string;
name: string;
};
function UserCard(props: UserCardProps) {
return <article>{props.name}</article>;
}
<UserCard id="u1" name="Ada" />;
<UserCard id="u1" />; // name 누락컴포넌트 반환 타입을 먼저 맞추려 하기보다 props 입력 계약을 명확히 두는 편이 실무적으로 더 중요합니다. 오류 대부분은 JSX 결과 타입보다 props 누락, optional 처리, callback 시그니처에서 발생합니다.
intrinsic element는 JSX namespace를 통해 검사된다
<div>, <span>, <button> 같은 intrinsic element는 JSX.IntrinsicElements에 선언된 속성 타입을 기준으로 검사됩니다. React 프로젝트에서는 React 타입 패키지가 이 선언을 제공하고, 커스텀 JSX 런타임에서는 해당 런타임이 타입을 제공해야 합니다.
declare namespace JSX {
interface IntrinsicElements {
"app-shell": {
title: string;
compact?: boolean;
};
}
}
<app-shell title="Dashboard" compact />;커스텀 element를 허용하려고 any로 넓게 열어 두면 잘못된 속성 이름도 모두 통과합니다. 디자인 시스템이나 웹 컴포넌트는 필요한 element만 명시적으로 선언하는 편이 안전합니다.
children은 props 계약의 일부다
JSX children은 특별한 마법 값처럼 보이지만 결국 props의 한 속성으로 검사됩니다. children을 허용하지 않는 컴포넌트, 반드시 하나의 render 함수만 받는 컴포넌트, 문자열만 받는 컴포넌트는 각각 다른 타입 계약을 가져야 합니다.
type PanelProps = {
title: string;
children: React.ReactNode;
};
function Panel({ title, children }: PanelProps) {
return (
<section>
<h2>{title}</h2>
{children}
</section>
);
}모든 컴포넌트에 자동으로 children을 허용하면 의도하지 않은 중첩 UI가 들어와도 타입이 막지 못합니다. children을 받지 않는 컴포넌트는 props 타입에 children을 넣지 않는 방식으로 계약을 좁히는 편이 좋습니다.
jsx 설정은 출력 코드와 런타임 요구사항을 바꾼다
tsconfig.json의 jsx 옵션은 JSX를 어떤 JavaScript로 내보낼지 결정합니다. React 17 이후 자동 runtime, 기존 React.createElement, preserve 출력은 빌드 도구와 런타임 기대가 다릅니다.
{
"compilerOptions": {
"jsx": "react-jsx"
}
}라이브러리, Next.js, Vite, Storybook처럼 빌드 도구가 JSX 변환을 담당하는 환경에서는 TypeScript의 타입 체크 설정과 실제 emit 경로를 함께 확인해야 합니다.
선택 기준
| 상황 | 먼저 볼 것 |
|---|---|
| 컴포넌트 속성 오류 | props 타입 |
| HTML 태그 속성 오류 | JSX.IntrinsicElements 제공 타입 |
| children 오류 | props의 children 선언 |
| 커스텀 JSX 런타임 | JSX namespace와 runtime export |
| 빌드 결과가 예상과 다름 | jsx compiler option |
React 프로젝트라도 JSX 타입 체크는 TypeScript의 규칙을 통과해야 합니다. 프레임워크가 자동으로 JSX runtime을 설정해 주더라도 컴포넌트 props, intrinsic element, children 계약은 타입 정의에서 결정됩니다.
주의할 점
JSX.Element, React.ReactNode, 컴포넌트 props 타입은 서로 다른 문제를 다룹니다.
반환값 타입을 넓게 맞추는 것으로 props 입력 오류나 children 계약 문제가 해결되지는 않습니다.
function Badge(): JSX.Element {
return <span>new</span>;
}
<Badge tone="info" />; // props를 받지 않으므로 오류가 맞음JSX 속성 타입을 피하려고 props를 any로 열면 컴포넌트 API 변경을 타입 시스템이 잡지 못합니다. 외부 데이터를 렌더링할 때도 데이터 검증과 props 타입은 분리해서 다루는 편이 안전합니다.
참고 링크
2 sources