Vue PropType으로 props 타입 좁히기
런타임 props 선언(props: { status: String })만으로는 TypeScript가 string, object, unknown[] 같은 넓은 타입밖에 알 수 없다. PropType<T>는 이 런타임 생성자에 TypeScript 타입을 덧씌워 더 구체적인 타입을 지정한다.
기본 사용법
import { defineComponent, type PropType } from 'vue';
type Status = 'success' | 'error' | 'warning';
export default defineComponent({
props: {
status: String as PropType<Status>,
},
});
status의 타입이 string이 아니라 'success' | 'error' | 'warning'으로 추론된다. 부모가 status="hello"를 넘기면 템플릿 타입 검사(vue-tsc, Volar)에서 에러가 난다.
다만 런타임 검사는 여전히 String 이다. 타입 검사 없이 실행되는 환경에서 잘못된 값이 들어와도 경고가 나지 않는다. 런타임에서도 막으려면 validator를 함께 쓴다.
props: {
status: {
type: String as PropType<Status>,
required: true,
validator: (value: string) => ['success', 'error', 'warning'].includes(value),
},
},
객체·배열 props
interface User {
id: number;
name: string;
}
props: {
user: Object as PropType<User>,
items: Array as PropType<string[]>,
onSelect: Function as PropType<(id: number) => void>,
},
Object만 쓰면 Record<string, any>, Array만 쓰면 unknown[]로 추론되어 자동완성과 검사를 받을 수 없다.
객체·배열의 기본값은 팩토리 함수로
props: {
user: {
type: Object as PropType<User>,
default: () => ({ id: 0, name: 'Guest' }),
},
items: {
type: Array as PropType<string[]>,
default: () => [],
},
},
객체·배열 기본값은 반드시 함수로 반환해야 한다. 함수는 컴포넌트 인스턴스마다 호출되어 새 객체를 만든다. 객체를 직접 넣으면 모든 인스턴스가 같은 객체를 공유하게 되므로 Vue가 이를 허용하지 않는다.
<script setup>에서는 타입 기반 선언
<script setup lang="ts">라면 PropType 없이 타입으로 바로 선언하는 편이 간결하다.
<script setup lang="ts">
const props = withDefaults(
defineProps<{
status: 'success' | 'error' | 'warning';
user?: User;
items?: string[];
}>(),
{
items: () => [],
},
);
</script>
Vue 3.5 이상에서는 const { items = [] } = defineProps<...>()처럼 구조 분해 기본값도 사용할 수 있다. PropType은 Options API나 defineComponent의 런타임 선언에서 필요하다.