React props 타입 지정 핵심
React에서 props 타입을 지정한다는 것은 자식 컴포넌트가 받을 수 있는 값의 모양을 미리 정하는 일입니다. 단순히 TypeScript 문법을 추가하는 것이 아니라, 부모 컴포넌트가 어떤 값을 넘겨야 하는지 명확하게 만드는 작업에 가깝습니다.
React만 사용할 때는 부모 컴포넌트에서 자식 컴포넌트로 값을 넘기는 일이 비교적 자유롭습니다. title이라는 props를 넘기기로 했지만 실수로 name을 넘겨도, 코드 작성 순간에는 조용히 넘어갈 수 있습니다. 화면에서 값이 비어 보이거나 특정 조건에서만 오류가 나야 문제를 알아차리는 경우도 많습니다.
TypeScript를 React에 붙이면 이 흐름이 달라집니다. 컴포넌트가 받을 props 구조를 타입으로 정해두기 때문에, 부모 컴포넌트에서 값을 잘못 넘기는 순간 에디터와 컴파일 단계에서 바로 확인할 수 있습니다. 그래서 props 타입은 자식 컴포넌트 내부를 위한 코드라기보다, 부모와 자식 사이에서 주고받는 값의 약속에 가깝습니다.
활용 02에서 React와 TypeScript의 기본 구조를 정리했다면, 이번 단계에서는 실제 컴포넌트 작성에서 가장 자주 만나는 props 타입 지정 방식을 정리하면 됩니다. children, 선택 props, 이벤트 타입은 뒤에서 따로 다룰 수 있으므로, 여기서는 일반적인 props 구조를 먼저 안정적으로 잡는 데 집중합니다.
React props 타입이 필요한 순간

props 타입이 가장 크게 느껴지는 순간은 컴포넌트를 작성할 때보다 사용할 때입니다. 자식 컴포넌트 안에서 title, price, isSoldOut을 사용하고 있는데, 부모 컴포넌트에서 price를 문자열로 넘기거나 isSoldOut을 빼먹으면 화면 결과가 예상과 달라질 수 있습니다.
예를 들어 상품 카드 컴포넌트를 만든다고 가정해보겠습니다. 화면에는 상품명, 가격, 품절 여부가 필요합니다. JavaScript에서는 이 값들이 어떤 타입이어야 하는지 코드만 보고 강하게 제한하기 어렵습니다. 하지만 TypeScript에서는 컴포넌트가 받아야 하는 props를 먼저 적어둘 수 있습니다.
type ProductCardProps = {
title: string;
price: number;
isSoldOut: boolean;
};
function ProductCard(props: ProductCardProps) {
return (
<article>
<h3>{props.title}</h3>
<p>{props.price.toLocaleString()}원</p>
<p>{props.isSoldOut ? '품절' : '구매 가능'}</p>
</article>
);
}
이 코드는 ProductCard가 아무 값이나 받지 않는다는 것을 보여줍니다. title은 문자열이어야 하고, price는 숫자여야 하며, isSoldOut은 참 또는 거짓이어야 합니다. 컴포넌트 내부에서 price.toLocaleString()을 바로 사용할 수 있는 이유도 price가 숫자라는 조건이 이미 타입으로 정해져 있기 때문입니다.
props 타입을 가장 단순하게 지정하는 방법
props 타입을 지정하는 가장 기본적인 방법은 컴포넌트 매개변수에 타입을 붙이는 것입니다. 처음에는 별도의 타입 이름을 만들지 않고 바로 적을 수도 있습니다.
function ProductCard(props: {
title: string;
price: number;
isSoldOut: boolean;
}) {
return (
<article>
<h3>{props.title}</h3>
<p>{props.price.toLocaleString()}원</p>
<p>{props.isSoldOut ? '품절' : '구매 가능'}</p>
</article>
);
}
이 방식은 짧은 예제에서는 이해하기 쉽습니다. 컴포넌트가 어떤 값을 받는지 바로 보이기 때문입니다. 다만 props가 2~3개를 넘어가거나 다른 곳에서 같은 타입을 참고해야 한다면 코드가 금방 답답해집니다. 그래서 실제 작업에서는 보통 props 타입을 따로 분리합니다.
구조 분해 할당을 함께 사용할 수도 있습니다. React 컴포넌트에서는 props.title처럼 쓰는 방식보다, 필요한 값을 바로 꺼내서 쓰는 형태가 자주 보입니다.
type ProductCardProps = {
title: string;
price: number;
isSoldOut: boolean;
};
function ProductCard({ title, price, isSoldOut }: ProductCardProps) {
return (
<article>
<h3>{title}</h3>
<p>{price.toLocaleString()}원</p>
<p>{isSoldOut ? '품절' : '구매 가능'}</p>
</article>
);
}
초보자가 자주 헷갈리는 부분은 구조 분해 할당 자체에 타입을 붙이는 위치입니다. { title, price } 각각에 타입을 따로 붙이는 것이 아니라, 구조 분해된 매개변수 전체가 어떤 props 타입을 따르는지 적어주는 방식으로 보면 됩니다.
여기서 중요한 점은 타입 선언이 JSX 바깥에 따로 떨어진 장식이 아니라는 점입니다. ProductCardProps가 바뀌면 컴포넌트 내부에서 사용할 수 있는 값도 바뀌고, 부모 컴포넌트에서 넘겨야 하는 값도 함께 바뀝니다. props 타입을 한 번 정하면 컴포넌트 작성 위치와 사용 위치가 같은 기준을 공유하게 됩니다.
Props 타입을 따로 분리하는 이유
컴포넌트 props 타입은 보통 ProductCardProps, ButtonProps, UserProfileProps처럼 컴포넌트 이름과 맞춰 작성합니다. 이렇게 이름을 붙이면 컴포넌트가 어떤 데이터를 요구하는지 위쪽에서 먼저 확인할 수 있습니다.
type ProductCardProps = {
title: string;
price: number;
thumbnailUrl: string;
isSoldOut: boolean;
};
타입을 분리하면 수정할 위치도 명확해집니다. 상품 카드에 썸네일 이미지가 추가되었다면 thumbnailUrl을 props 타입에 추가하고, 컴포넌트 내부에서 해당 값을 사용하면 됩니다. 반대로 더 이상 쓰지 않는 props가 있다면 타입과 JSX를 함께 정리할 수 있습니다.
type과 interface 중 무엇을 써야 하는지도 자주 나오는 질문입니다. props 타입은 type으로 작성해도 되고 interface로 작성해도 됩니다. 이 단계에서는 둘 중 하나가 절대적으로 맞다는 식으로 외우기보다, 프로젝트 안에서 한 가지 방식으로 일관되게 쓰는 것이 더 중요합니다.
interface ProductCardProps {
title: string;
price: number;
thumbnailUrl: string;
isSoldOut: boolean;
}
개인 프로젝트나 학습용 코드에서는 type으로 시작해도 충분합니다. 이후 팀 코드에서 interface를 쓰고 있다면 그 컨벤션을 따르면 됩니다. props 타입을 처음 배우는 단계에서는 type과 interface 차이를 깊게 파고들기보다, 컴포넌트가 받는 값의 구조를 정확히 적는 것이 먼저입니다.
부모 컴포넌트에서 props 타입이 작동하는 방식

props 타입의 효과는 자식 컴포넌트보다 부모 컴포넌트에서 더 선명하게 보입니다. ProductCard가 title, price, thumbnailUrl, isSoldOut을 필수로 받도록 되어 있다면, 이 컴포넌트를 사용하는 곳에서도 그 조건을 맞춰야 합니다.
function ProductList() {
return (
<section>
<ProductCard
title="무선 키보드"
price={49000}
thumbnailUrl="/images/keyboard.png"
isSoldOut={false}
/>
</section>
);
}
여기서 price를 문자열로 넘기면 TypeScript는 타입이 맞지 않는다고 알려줍니다.
function ProductList() {
return (
<section>
<ProductCard
title="무선 키보드"
price="49000"
thumbnailUrl="/images/keyboard.png"
isSoldOut={false}
/>
</section>
);
}
사람이 보기에는 49000처럼 보이지만, 따옴표로 감싼 값은 문자열입니다. ProductCardProps에서 price를 number로 정했다면 price={49000}처럼 중괄호 안에 숫자로 넘겨야 합니다. 이 차이를 초반에 잡아두면 가격 계산, 정렬, 할인율 계산 같은 로직에서 불필요한 오류를 줄일 수 있습니다.
필수 props를 빼먹어도 오류가 납니다. 예를 들어 thumbnailUrl이 타입에 포함되어 있는데 부모 컴포넌트에서 넘기지 않으면, TypeScript는 해당 props가 빠졌다고 알려줍니다.
function ProductList() {
return (
<section>
<ProductCard
title="무선 키보드"
price={49000}
isSoldOut={false}
/>
</section>
);
}
이 오류는 불편해 보일 수 있지만, 실제로는 컴포넌트가 제대로 렌더링되기 위한 조건을 알려주는 신호입니다. 상품 카드 디자인에서 썸네일이 반드시 필요하다면 thumbnailUrl은 필수 props로 두는 것이 맞습니다. 반대로 이미지가 없어도 기본 이미지로 대체할 수 있다면 선택 props로 바꾸는 흐름을 고민할 수 있습니다. 선택 props와 기본값은 다음 단계에서 따로 분리해 정리하는 것이 좋습니다.
boolean props도 초반에 실수가 자주 납니다. JSX에서 isSoldOut="false"처럼 문자열을 넘기면 사람이 보기에는 거짓처럼 느껴지지만, 타입 기준으로는 boolean이 아닙니다. 참과 거짓을 표현할 때는 문자열이 아니라 {false}, {true}처럼 JavaScript 값으로 넘기는 습관을 들이는 편이 좋습니다.
function ProductList() {
return (
<ProductCard
title="무선 키보드"
price={49000}
thumbnailUrl="/images/keyboard.png"
isSoldOut="false"
/>
);
}
위 코드는 isSoldOut이 문자열이기 때문에 props 타입과 맞지 않습니다. 단순 UI 조건문에서는 이런 차이가 특히 크게 드러납니다. 품절 배지, 비활성 버튼, 노출 여부처럼 boolean으로 화면을 나누는 값은 처음부터 boolean 타입으로 고정해두는 것이 관리하기 쉽습니다.
실제 UI 컴포넌트 기준으로 props 정리하기
props 타입을 정할 때는 “이 값이 어떤 타입인가”만 보지 말고, “이 컴포넌트를 사용하는 사람이 무엇을 반드시 넘겨야 하는가”를 함께 봐야 합니다. 상품 카드라면 상품명과 가격은 필수일 가능성이 높습니다. 품절 여부도 화면에서 배지를 보여줘야 한다면 필수로 둘 수 있습니다.
type ProductCardProps = {
title: string;
price: number;
thumbnailUrl: string;
isSoldOut: boolean;
onClick: () => void;
};
function ProductCard({
title,
price,
thumbnailUrl,
isSoldOut,
onClick,
}: ProductCardProps) {
return (
<button type="button" onClick={onClick} disabled={isSoldOut}>
<img src={thumbnailUrl} alt="" />
<strong>{title}</strong>
<span>{price.toLocaleString()}원</span>
<em>{isSoldOut ? '품절' : '구매 가능'}</em>
</button>
);
}
여기서 onClick은 함수 props입니다. () => void는 인자를 받지 않고 반환값을 사용하지 않는 함수라는 뜻입니다. 버튼 클릭 시 상세 페이지로 이동하거나 모달을 여는 작업을 부모 컴포넌트에서 결정하고, ProductCard는 클릭 가능한 UI만 담당하도록 분리할 수 있습니다.
function ProductList() {
const handleProductClick = () => {
console.log('상품 상세로 이동');
};
return (
<ProductCard
title="무선 키보드"
price={49000}
thumbnailUrl="/images/keyboard.png"
isSoldOut={false}
onClick={handleProductClick}
/>
);
}
함수 props까지 타입으로 지정하면 컴포넌트의 책임이 더 분명해집니다. 자식 컴포넌트는 클릭이 발생했다는 사실만 부모에게 넘기고, 실제로 어떤 동작을 할지는 부모 컴포넌트가 결정합니다. 이 구조는 버튼, 카드, 리스트 아이템처럼 재사용되는 UI에서 자주 쓰입니다.
다만 함수 props의 이벤트 객체까지 다루기 시작하면 이야기가 조금 길어집니다. onClick, onChange, onSubmit 같은 이벤트 타입은 React 이벤트 타입과 연결되므로 별도 글에서 정리하는 편이 흐름이 깔끔합니다. 이번 글에서는 함수 props의 기본 형태를 이해하는 정도면 충분합니다.
props 타입을 너무 넓게 잡지 않기
props 타입을 지정할 때 가장 피해야 할 방식은 전부 any로 열어두는 것입니다. 처음에는 오류가 사라져 편해 보이지만, 실제로는 TypeScript가 확인할 수 있는 정보가 거의 없어집니다.
type ProductCardProps = {
title: any;
price: any;
isSoldOut: any;
};
이렇게 작성하면 price에 문자열을 넘겨도 TypeScript가 막지 못합니다. isSoldOut에 문자열을 넘겨도 통과할 수 있고, 컴포넌트 내부에서는 참과 거짓을 기대했는데 예상과 다른 렌더링이 나올 수 있습니다. 타입을 쓰는 목적이 값의 구조를 미리 확인하는 것이라면, any는 그 장점을 대부분 포기하는 선택입니다.
그렇다고 모든 타입을 지나치게 좁게 잡을 필요도 없습니다. 예를 들어 상품 상태가 정해진 값 중 하나라면 string보다 유니온 타입이 더 정확할 수 있습니다.
type ProductStatus = 'available' | 'soldOut' | 'reserved';
type ProductCardProps = {
title: string;
price: number;
status: ProductStatus;
};
이렇게 작성하면 status에 아무 문자열이나 들어갈 수 없습니다. available, soldOut, reserved 중 하나만 허용됩니다. 다만 처음 props 타입을 배울 때 모든 값을 유니온 타입으로 바꾸려고 하면 오히려 복잡해질 수 있습니다. 먼저 string, number, boolean, 함수 타입으로 기본 구조를 익히고, 값의 범위가 명확히 정해진 경우에만 유니온 타입을 적용하면 됩니다.
이번 글에서 다루지 않는 것들
props 타입을 정리하다 보면 자연스럽게 다른 질문으로 이어집니다. 대표적으로 children 타입이 있습니다. 일반 props는 부모가 속성처럼 넘기는 값이지만, children은 컴포넌트 태그 사이에 들어가는 내용입니다.
<Card>
<p>카드 안에 들어갈 내용</p>
</Card>
이 구조는 title, price처럼 단순한 props와 성격이 다릅니다. 그래서 이번 글에서 함께 처리하기보다 다음 단계인 children 타입 지정하기에서 따로 정리하는 흐름이 더 자연스럽습니다.
선택 props도 마찬가지입니다. 어떤 값은 반드시 있어야 하지만, 어떤 값은 없어도 기본값으로 대체할 수 있습니다. 예를 들어 thumbnailUrl이 없을 때 기본 이미지를 보여줄 수 있다면 필수 props가 아니라 선택 props로 설계할 수 있습니다. 이때는 ? 문법과 기본값 처리 기준이 같이 필요하므로 optional props와 default value 처리하기에서 따로 다루는 편이 좋습니다.
이벤트 타입도 이번 글에서는 깊게 들어가지 않습니다. onClick: () => void 정도는 함수 props의 기본 형태로 이해할 수 있지만, 실제 버튼 클릭 이벤트 객체나 input 변경 이벤트를 받기 시작하면 React.MouseEvent, React.ChangeEvent 같은 타입이 필요해집니다. 이 부분은 이벤트 타입 글에서 별도로 보는 것이 혼란을 줄입니다.
정리: props 타입은 컴포넌트의 사용 조건이다
React 컴포넌트 props 타입은 단순히 TypeScript 문법을 추가하는 작업이 아닙니다. 컴포넌트가 어떤 값을 받아야 화면을 제대로 그릴 수 있는지 명확히 정하는 작업입니다. 자식 컴포넌트 입장에서는 내부에서 안전하게 값을 사용할 수 있고, 부모 컴포넌트 입장에서는 잘못된 값을 넘겼을 때 바로 확인할 수 있습니다.
처음에는 type Props = { ... } 형태로 시작하면 충분합니다. 컴포넌트 이름에 맞춰 ProductCardProps, ButtonProps, UserProfileProps처럼 타입 이름을 붙이고, 필요한 값을 string, number, boolean, 함수 타입으로 하나씩 정리하면 됩니다. 이 단계에서 중요한 것은 고급 타입을 많이 쓰는 것이 아니라, 컴포넌트가 실제로 요구하는 값과 타입 선언이 어긋나지 않게 만드는 것입니다.
props 타입을 제대로 잡아두면 컴포넌트를 수정할 때 기준점이 생깁니다. 새 props가 필요하면 타입에 추가하고, 더 이상 필요 없는 props는 타입과 JSX에서 함께 제거하면 됩니다. 이 기준이 생기면 컴포넌트를 사용하는 위치도 함께 안전하게 정리할 수 있습니다.
다음 단계에서는 일반 props와 자주 헷갈리는 children 타입을 따로 분리해 보면 됩니다. props 타입의 기본 구조를 이해한 뒤 children, 선택 props, 이벤트 타입으로 넘어가면 React TypeScript 컴포넌트 작성 흐름이 훨씬 안정적으로 이어집니다.