Vue Nation 2024…
Day 2
에는 여러 훌륭한 세션이 있었습니다.
약간의 실망은 UI 라이브러리
에 대한 소개가 기술적인 솔루션보다 많았기 때문입니다.
하지만, 저는 사용하지 않는 라이브러리들이 2024년에 많은 개발 계획
을 가지고 있다는 것을 보았고, 나중에 사용하고 소개할 기회를 만들어야 한다고 생각했습니다. (Prime Vue
와 Vuetify
가 저의 관심을 끌었습니다!)
Day 2
이후, 저는 다음과 같은 인사이트를 얻었습니다. 이는 이 포스트 이후에 순차적으로 소개할 예정입니다!
- 타입 안전한 컴포넌트 만들기 (Generic 타입 사용)
- Playwright & Vitest를 사용하여 Vue3 애플리케이션 테스트
- Vue.js 사용 시 발생하는 일반적인 오류와 방지 방법
오늘의 주제는 타입 안전한 컴포넌트 만들기 (Generic 타입 사용)
입니다.
Generically Typed Components
지원
Vue 3.3에서 먼저, 이 포스트를 작성할 수 있게 해준 Abdelrahman Awad
에게 감사의 말씀을 전합니다.
그는 정말 훌륭한 세션이었습니다!
Vue 3.3는 2023년 3월에 공식적으로 출시되었습니다. 그 중 하나는 Generically Typed Vue Components를 지원하는 것이었습니다.
위의 블로그에서 설명한 것처럼, <script setup>
태그에서 generic
속성을 통해 타입 매개변수를 설정할 수 있습니다. 아래와 같이 할 수 있습니다.
<script setup lang='ts' generic='T'>
defineProps<{
items: T[].
selectedItem: T
}>()
</script>
또한, 아래와 같이 extends
를 사용하여 여러 매개변수
, 제약 조건, 기본 타입, 가져온 타입을 확장할 수 있습니다.
<script setup lang='ts' generic='T extends string | number, U extends Item'>
import type { Item } from './types'
defineProps<{
label: T,
itemList: U[]
}>()
</script>
이를 통해, 두 개 이상의 속성이 컴포넌트에 전달되었는지 여부를 명확하게 타입 검사
할 수 있습니다. (타입이 다르면, Volar plugin
은 빨간색 선으로 표시합니다!)
이제 프로젝트를 만들어봅시다!
아래는 저가 직접 작성한 간단한 예제입니다.
(예제에서 Nuxt UI를 사용했습니다. 나중에
이에 대한 포스트를 작성할 예정입니다!)
케이스 1: Input
// /components/A/Card.vue
<script setup lang="ts" generic="T extends string | number">
defineProps<{
label: T
cardType: T
}>()
const model = defineModel<T extends 'number' ? number : string>()
</script>
<template>
<p>
{{ label }}:
</p>
<div>
<DDInput
v-model="model"
:type="(cardType as string)"
/>
</div>
</template>
///page/index.vue
<script setup lang="ts">
const text = ref('')
const count = ref(0)
</script>
<template>
<div class="w-full min-h-screen flex justify-center items-center gap-8">
<div class="flex flex-col gap-2">
<ACard
v-model="text"
label="string"
:card-type="'string'"
/>
<pre>
{{ { value: text, type: typeof text } }}
</pre>
</div>
<div class="flex flex-col gap-2">
<ACard
v-model="count"
label="number"
:card-type="'number'"
/>
<pre>
{{ { value: count, type: typeof count } }}
</pre>
</div>
</div>
</template>
위의 예제는 다음과 같이 구성되어 있습니다.
ACard.vue
컴포넌트는label
과cardType
을 속성으로 받아defineModel
을 통해 반응형 값을 허용합니다.index.vue
에서, 특정 타입의 값이 전달되지 않으면, 빨간색 선으로 표시되며, 타입 추론이 가능합니다. 또한,ACard
컴포넌트의modelValue
는index.vue
에서 제공된 타입에 따라 필요한 타입을 추론합니다. (저는 검색했지만, Volar VSCode 플러그인이 자동으로 누락된 매개변수를 표시하는 것 같습니다. 하지만, 저는 그렇게 할 수 없고, 더 자세히 알아보아야 합니다.)
(내 VSCode에 무슨 일이 있었나…)
케이스 2: SelectMenu
아래 예제는 SelectMenu
를 사용한 간단한 예제입니다. 이는 Generic 컴포넌트를 사용하여 컴포넌트의 타입 추론을 허용합니다.
// /components/A/List.vue
<script setup lang="ts" generic="T extends Option">
import type { Option } from '~/types/index'
const props = defineProps<{
modelValue: T
options: T[]
}>()
const emit = defineEmits<{
'update:model': [value: T]
}>()
const computedModel = computed({
get () {
return props.modelValue
},
set (value: T) {
emit('update:model', value)
}
})
</script>
<template>
<div>
<DDSelectMenu
v-model="computedModel"
:options="options"
>
<template #leading>
<span>
{{ computedModel.id }}
</span>
</template>
</DDSelectMenu>
</div>
</template>
// /pages/select.vue
<script setup lang="ts">
import type { Option } from '~/types/index'
const options: Option[] = [
{ id: 1, label: 'banana' },
{ id: 2, label: 'orange' },
{ id: 3, label: 'apple' },
{ id: 10, label: 'strowberry' }
]
const selectedOption = ref<Option>(options[0])
</script>
<template>
<div class="h-screen w-full flex flex-col justify-center items-center gap-8">
<AList
v-model="selectedOption"
:options="options"
@update:model="(value) => selectedOption = value"
/>
<pre>
{{ { value: selectedOption, type: typeof selectedOption } }}
</pre>
</div>
</template>
// /types/index.ts
export interface Option {
id: string | number
label: string
}
modelValue
에 다른 타입의 값을 전달하면, 빨간색 선으로 표시되며, 타입 추론이 가능합니다.
// /pages/select.vue
<script setup lang="ts">
import type { Option } from '~/types/index'
const options: Option[] = [
{ id: 1, label: 'banana' },
{ id: 2, label: 'orange' },
{ id: 3, label: 'apple' },
{ id: 10, label: 'strowberry' }
]
const selectedOption = ref<Option>(options[0])
const selectedSometing = ref<{id: number, value: string}>({ id: 1, value: 'banana' })
</script>
<template>
<div class="h-screen w-full flex flex-col justify-center items-center gap-8">
<AList
v-model="selectedSometing"
:options="options"
@update:model="(value) => selectedOption = value"
/>
<pre>
{{ { value: selectedOption, type: typeof selectedOption } }}
</pre>
</div>
</template>
이제 프로젝트를 만들어봅시다!
저는 아직 완전히 사용하지 않았지만, Generic Component
는 DX 경험
을 크게 향상시킬 것이라고 생각합니다.
Volar plugin
에서 누락된 매개변수를 추론하는 기능이 제대로 작동하지 않는 이유를 확인하는 것이 가치가 있는지 확인해야 합니다!!
이 세션에서 얻은 인사이트입니다.
문제가 있거나 개선할 부분이 있으면, 댓글
을 남겨주세요!
피드백은 언제나 환영합니다!
다음에 봐요!