Vue 3 + TypeScript 설계 가이드
시작은 표준 템플릿으로 충분하다
npm create vite@latest my-app -- --template vue-ts
cd my-app
npm install
프로젝트 초반에는 설정을 늘리기보다 strict 기반에서 컴포넌트와 도메인 모델을 안정적으로 분리하는 습관이 더 중요합니다.
props와 emits는 컴포넌트 계약의 핵심
<script setup lang="ts">
interface Props {
title: string
disabled?: boolean
}
const props = defineProps<Props>()
const emit = defineEmits<{
submit: [id: number]
}>()
</script>
이런 방식은 부모와 자식 사이의 계약을 타입으로 드러내 줍니다. 특히 이벤트 payload가 분명해져서 상호작용 흐름을 읽기 쉬워집니다.
ref와 reactive는 목적에 따라 고른다
단일 원시값이나 명확히 교체되는 값은 ref가 직관적입니다. 반면 관련 필드를 묶어 다룰 때는 reactive가 편할 수 있습니다. 다만 구조 분해 시 반응성이 깨질 수 있으므로 사용 맥락을 이해하는 것이 중요합니다.
API 타입과 UI 타입을 그대로 섞지 않기
백엔드 응답 모델을 화면 컴포넌트 타입으로 그대로 사용하면 프런트엔드가 응답 구조에 과하게 묶입니다. API DTO, 화면 ViewModel, 폼 입력 모델을 필요에 따라 나누는 편이 변경에 강합니다.
composable은 타입이 아니라 책임이 먼저다
TypeScript가 강하다고 해서 모든 로직을 generic composable로 만들 필요는 없습니다. 실제로 반복되는 책임이 있을 때만 추상화하는 편이 좋습니다. 타입 시스템은 그 책임을 안전하게 표현하는 보조 장치여야 합니다.
마무리
Vue 3 + TypeScript의 핵심은 복잡한 타입 기교가 아니라 안정적인 계약입니다. props, emits, composable, 상태 모델을 분리해서 생각하면 타입은 개발 속도를 늦추는 규칙이 아니라 협업을 돕는 인터페이스가 됩니다.
운영 환경에서 어려워지는 지점
- 템플릿 추론이 안전해 보여도 store, composable, API 경계에서 암묵적
any가 새면 전체 앱의 타입 안정성은 약해진다. - props, emits, 비동기 데이터 형태를 컴포넌트마다 다르게 추론하면 장기적으로 일관성이 무너진다.
- 공용 타입과 composable의 계층 규칙이 없으면 Vue 코드베이스는 빠르게 탐색하기 어려워진다.
중요한 아키텍처 결정
- strict TypeScript와 함께 Volar 친화적인 props, emits, composable 규칙을 초기에 잡는다.
ref,reactive에 원본 API payload를 그대로 넣지 말고 transport, domain, presentation 모델을 분리한다.- composable을 공개 API로 보고 반환 타입을 안정적으로 설계한다.
실무 예시
좋은 기본 패턴은 서버 응답을 반응형 상태로 넣기 전에 정규화하는 것이다.
type ProductResponse = {
id: string
product_name: string
stock_count: number
}
type ProductViewModel = {
id: string
name: string
isSoldOut: boolean
}
function toProductViewModel(product: ProductResponse): ProductViewModel {
return {
id: product.id,
name: product.product_name,
isSoldOut: product.stock_count <= 0,
}
}
피해야 할 안티패턴
- 원본 API 객체를 컴포넌트 상태에 그대로 두고 템플릿 트리 전체에서 수정하는 것.
- 도메인이 명확한데도 넓은
Record<string, unknown>으로 얼버무리는 것. - 화면마다 반환 모양이 달라지는 composable을 만드는 것.
운영 체크리스트
- 빌드와 별도로 CI에서 타입 체크를 실행한다.
- 공용 컴포넌트 경계에서는
defineProps,defineEmits시그니처를 명시적으로 유지한다. - API 스키마가 바뀌면 store와 composable 계약도 함께 점검한다.
- 프로젝트가 커질수록 에디터와 CI 타입 검사 지연을 추적한다.
최종 판단
Vue 3와 TypeScript의 조합은 타입이 composable 경계와 컴포넌트 계약을 강화할 때 가장 강하다. 템플릿 추론만으로는 장기 아키텍처를 지키기 어렵다.
Continue Reading
다음으로 읽기 좋은 글
Vue.js 아키텍처 설계 가이드
Vue.js 프로젝트를 단순 컴포넌트 모음이 아니라 유지보수 가능한 프런트엔드 시스템으로 설계하는 방법을 정리합니다. 상태 경계, 라우팅, 데이터 흐름, 폴더 구조, 성능과 협업 관점까지 실무 중심으로 다룹니다.
🖥️ FrontendVue 컴포넌트 설계 원칙 가이드
Vue 애플리케이션에서 컴포넌트 책임, 재사용 경계, props/events 설계, 슬롯 전략을 실무 관점에서 정리합니다.
💬 LanguageTypeScript Utility Types 실전 가이드
TypeScript 유틸리티 타입을 DTO, 업데이트 payload, selector, 파생 타입 설계에 어떻게 써야 하는지, 어디서부터는 가독성을 해치는지 정리합니다.
💬 LanguageTypeScript Generics 실전 가이드
TypeScript 제네릭을 어디서 쓰면 API 계약이 더 강해지고, 어디서부터는 타입 퍼즐이 되는지 실무 기준으로 정리합니다.
다음 탐색