레슨 3 / 9·20분
컴포넌트와 Props
Vue 컴포넌트 정의하기
Vue에서 컴포넌트는 재사용 가능한 UI 조각입니다. 싱글 파일 컴포넌트(SFC, .vue 파일)에서는 , , 세 블록으로 구성됩니다. 문법을 사용하면 defineComponent 없이도 간결하게 컴포넌트를 작성할 수 있습니다.
vue
<!-- AlertBox.vue -->
<script setup lang="ts">
// defineProps로 props 선언
const props = defineProps<{
title: string;
type?: 'info' | 'warning' | 'error';
}>();
// 기본값이 필요하면 withDefaults 사용
const { type } = withDefaults(defineProps<{
title: string;
type?: 'info' | 'warning' | 'error';
}>(), {
type: 'info',
});
</script>
<template>
<div :class="['alert', 'alert-' + type]">
<strong>{{ title }}</strong>
<slot /> <!-- 부모가 전달한 내용이 여기에 렌더링 -->
</div>
</template>
<style scoped>
.alert {
padding: 16px;
border-radius: 8px;
margin-bottom: 12px;
}
.alert-info { background-color: #e0f2fe; color: #0c4a6e; }
.alert-warning { background-color: #fef3c7; color: #92400e; }
.alert-error { background-color: #fee2e2; color: #991b1b; }
</style>emit으로 자식 -> 부모 통신
Props가 부모에서 자식으로 데이터를 전달하는 것이라면, emit은 자식이 부모에게 이벤트를 발생시켜 통신하는 방법입니다. defineEmits로 발생시킬 이벤트를 선언하고, 부모에서 @이벤트명으로 수신합니다.
vue
<!-- SearchInput.vue (자식 컴포넌트) -->
<script setup lang="ts">
const emit = defineEmits<{
search: [query: string];
clear: [];
}>();
const query = ref('');
function handleSearch() {
emit('search', query.value);
}
function handleClear() {
query.value = '';
emit('clear');
}
</script>
<template>
<div class="search-input">
<input
v-model="query"
placeholder="검색어 입력"
@keyup.enter="handleSearch"
/>
<button @click="handleSearch">검색</button>
<button @click="handleClear">초기화</button>
</div>
</template>vue
<!-- App.vue (부모 컴포넌트) -->
<script setup lang="ts">
import SearchInput from './SearchInput.vue';
import { ref } from 'vue';
const results = ref<string[]>([]);
function onSearch(query: string) {
console.log('검색어:', query);
// API 호출 등 검색 로직 실행
}
function onClear() {
results.value = [];
}
</script>
<template>
<div>
<SearchInput @search="onSearch" @clear="onClear" />
<ul>
<li v-for="item in results" :key="item">{{ item }}</li>
</ul>
</div>
</template>Slots과 Provide/Inject
slot은 부모가 자식 컴포넌트 내부에 콘텐츠를 삽입할 수 있게 합니다. provide/inject는 깊이 중첩된 컴포넌트 간에 props를 일일이 전달하지 않고도 데이터를 공유하는 방법입니다.
vue
<!-- 부모: provide로 데이터 제공 -->
<script setup lang="ts">
import { provide, ref } from 'vue';
const theme = ref<'light' | 'dark'>('light');
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light';
}
// 'theme' 키로 데이터 제공 (자손 컴포넌트 어디서든 접근 가능)
provide('theme', { theme, toggleTheme });
</script>
<!-- 자손 컴포넌트: inject로 데이터 주입 -->
<script setup lang="ts">
import { inject } from 'vue';
import type { Ref } from 'vue';
const { theme, toggleTheme } = inject<{
theme: Ref<'light' | 'dark'>;
toggleTheme: () => void;
}>('theme')!;
</script>
<template>
<div :class="theme">
<p>현재 테마: {{ theme }}</p>
<button @click="toggleTheme">테마 전환</button>
</div>
</template>💡
provide/inject는 전역 상태 관리의 간단한 대안입니다. 하지만 대규모 애플리케이션에서는 데이터 흐름을 추적하기 어려워질 수 있으므로, 복잡한 상태 관리가 필요하다면 Pinia와 같은 전용 상태 관리 라이브러리를 사용하는 것이 좋습니다.