단일 파일 컴포넌트의 심화과정이다.
학습 난이도가 쉽지 않아 정리 과정에서 대표 강사님의 정리본을 참고하였다.
1. 단일 파일 컴포넌트에서의 스타일
컴포넌트 내에서 <style> 태그를 이용하여 스타일을 지정하였을 경우
class명이 겹쳤을 때, 전체 컴포넌트에 적용하는 전역 스타일(main.css)와 충돌이 발생할 수 있음.
범위 CSS
전역 스타일(main.css)과의 충돌을 피함
스타일 태그 뒤에 scoped를 추가하여 사용 : <style scoped>
scoped
를 사용하면 해당 컴포넌트에만 스타일이 적용됨
특정 컴포넌트애만 스타일을 적용하고 싶을 때 사용. (많이 사용됨)
CSS 모듈
CSS 스타일을 객체
처럼 불러와서 사용할 수 있음
<template>
<p :class="$style.red">이것은 빨간색이어야 합니다.</p>
</template>
<style module>
.red {
color: red;
}
</style>
CSS 범위를 현재 컴포넌트로만 지정하는 것과 동일한 효과
2. 슬롯(Slot)
슬롯(Slot)
: Vue 컴포넌트에서 콘텐츠를 전달하는 강력한 방법
컴포넌트를 더 유연하고 재사용 가능하게 만들어주는 중요한 기능
슬롯 = 구멍
부모 컴포넌트에서 자식 컴포넌트로 HTML 내용을 전달할 수 있게 해주는 창구 역할
예를 들어, 버튼 컴포넌트를 만들 때:
- 버튼의 스타일, 동작은 같지만
- 버튼 안의 텍스트나 아이콘은 다르게 하고 싶을 때 슬롯을 사용
슬롯을 사용하기 전에는 자식 컴포넌트가 정해놓은 템플릿만 불러올 수 있었음.
슬롯의 장점
- 컴포넌트 재사용성 증가: 같은 구조에 다른 내용을 넣을 수 있다.
- 유연한 컴포넌트 설계: 부모가 자식 컴포넌트의 일부를 커스터마이징할 수 있다.
- 컴포넌트 간 통신: 범위가 있는 슬롯으로 자식의 데이터를 부모에서 사용할 수 있다.
기본 사용법
- 자식 컴포넌트에서
<slot></slot>
태그를 배치 - 부모 컴포넌트에서 자식 컴포넌트 태그 사이에 내용을 추가
<!-- 자식 컴포넌트: CustomButton.vue (Options API) -->
<template>
<button class="custom-btn">
<slot></slot>
<!-- 여기에 부모로부터 받은 내용이 들어갑니다 -->
</button>
</template>
<script>
export default {
name: 'CustomButton',
}
</script>
<!-- 부모 컴포넌트 -->
<template>
<div>
<CustomButton>저장하기</CustomButton>
<CustomButton>취소</CustomButton>
</div>
</template>
명명된 슬롯 (Named Slots)
이름 있는 슬롯으로 부르기도 함.
하나의 컴포넌트에 여러 슬롯을 만들고 싶을 때는 명명된 슬롯을 사용.
화면의 레이아웃을 관리할 목적으로 많이 사용됨.
<!-- 자식 컴포넌트: Card.vue -->
<template>
<div class="card">
<div class="card-header">
<slot name="header">기본 헤더</slot>
</div>
<div class="card-body">
<slot>기본 내용</slot>
<!-- 이름이 없는 슬롯은 default 슬롯이 됩니다 -->
</div>
<div class="card-footer">
<slot name="footer">기본 푸터</slot>
</div>
</div>
</template>
부모 컴포넌트에서는 v-slot
디렉티브를 사용하여 각 슬롯에 내용을 제공
<!-- 부모 컴포넌트 -->
<template>
<Card>
<template v-slot:header>
<h2>카드 제목</h2>
</template>
<p>카드 본문 내용입니다.</p>
<!-- 이름 없는 슬롯(default)에 들어갑니다 -->
<template v-slot:footer>
<button>자세히 보기</button>
</template>
</Card>
</template>
범위 슬롯(Scoped Slots)
속성을 전달하듯 자식에서 부모로 데이터를 전달할 수 있다.
그래서 자식 컴포넌트의 데이터를 부모 컴포넌트에서 사용하고 싶을 때 범위가 있는 슬롯을 사용
전달된 데이터는 슬롯 템플릿 내부 범위에서만 사용할 수 있음
- 일반 슬롯 : 부모 → 자식으로 HTML 내용을 전달
- 범위 슬롯 : 부모 → 자식으로 HTML을 전달하고, 자식 → 부모로 데이터를 전달
쉽게 생각해서 “템플릿 함수”
자식 컴포넌트가 데이터를 제공하고, 부모 컴포넌트는 그 데이터를 사용해 최종 출력 형태를 결정
자식:
<!-- 자식 컴포넌트: UserList.vue -->
<template>
<ul>
<li v-for="user in users" :key="user.id">
<slot :user="user">
{{ user.name }}
<!-- 기본값 -->
</slot>
</li>
</ul>
</template>
<script>
export default {
data() {
return {
users: [
{ id: 1, name: '김철수', role: '관리자' },
{ id: 2, name: '이영희', role: '사용자' },
],
}
},
}
</script>
부모 컴포넌트에서는:
<!-- 부모 컴포넌트 -->
<template>
<div>
<UserList>
<!-- slotProps로 자식의 데이터를 받습니다 -->
<template v-slot:default="slotProps">
<div class="user-item">
<strong>{{ slotProps.user.name }}</strong>
<span class="badge">{{ slotProps.user.role }}</span>
<small>순서: {{ slotProps.index + 1 }}</small>
</div>
</template>
</UserList>
</div>
</template>
축약형:
<UserList>
<template v-slot:default="{ user, index }">
<div class="user-item">
<strong>{{ user.name }}</strong>
<span class="badge">{{ user.role }}</span>
<small>순서: {{ index + 1 }}</small>
</div>
</template>
</UserList>
스코프 슬롯의 장점
- 관심사 분리 : 데이터 로직(자식)과 표현 로직(부모)을 분리 가능
- 재사용성: 하나의 컴포넌트로 다양한 UI를 구현
- 유연성: 컴포넌트 사용자가 렌더링 방식을 자유롭게 결정
3. 동적 컴포넌트(Dynamic Component)
동적 컴포넌트 : 여러 컴포넌트 중에서 어떤 컴포넌트를 렌더링할 지 실시간으로 전환할 수 있게 해주는 기능
이를 통해 같은 위치에서 다양한 컴포넌트를 동적으로 표시할 수 있다.
<component>
태그와 v-bind 디렉티브를 이용해 is
속성을 사용하여 동적 컴포넌트를 구현
컴포넌트 명을 문자열로 대입 - 대소문자 구분함
<component :is="currentComponent"></component>
여기서 currentComponent
는
- 등록된 컴포넌트 이름(문자열)
- 컴포넌트 객체
- 비동기 컴포넌트
중 하나가 될 수 있다.
실생활 비유로 이해하기
"TV 채널 변경"
- TV(컨테이너)는 그대로지만 리모컨(변수)으로 채널(컴포넌트) 변경
- 같은 화면에 다른 프로그램이 표시
- 탭 인터페이스
<template>
<div class="tab-container">
<div class="tab-buttons">
<button
v-for="tab in tabs"
:key="tab.name"
@click="activeTab = tab.component"
:class="{ active: activeTab === tab.component }"
>
{{ tab.name }}
</button>
</div>
<div class="tab-content">
<component :is="activeTab"></component>
</div>
</div>
</template>
<script>
import HomeTab from './HomeTab.vue'
import UsersTab from './UsersTab.vue'
import SettingsTab from './SettingsTab.vue'
export default {
components: {
HomeTab,
UsersTab,
SettingsTab,
},
data() {
return {
activeTab: 'HomeTab',
tabs: [
{ name: '홈', component: 'HomeTab' },
{ name: '사용자', component: 'UsersTab' },
{ name: '설정', component: 'SettingsTab' },
],
}
},
}
</script>
2. 폼 위젯 선택기
<template>
<div>
<select v-model="selectedWidget">
<option value="TextInput">텍스트 입력</option>
<option value="Dropdown">드롭다운</option>
<option value="DatePicker">날짜 선택</option>
</select>
<component :is="selectedWidget" v-model="formValue" :options="widgetOptions"></component>
</div>
</template>
4. 컴포넌트에서의 V-model
v-model
: 양방향 데이터 바인딩을 수행하는 디렉티브
v-model은 다음 두 가지를 한 번에 처리
- 부모 컴포넌트의 값을 자식 컴포넌트에 전달(props)
- 자식 컴포넌트에서 발생한 변경 사항을 부모 컴포넌트에게 알림(events)
장점
- 코드 간결성 : 양방향 바인딩을 간결하게 표현
- 재사용성 : 컴포넌트를 다양한 상황에서 쉽게 재사용
- 유지보수: 데이터 흐름이 명확하여 유지보수 용이
- 개발 효율성: 양방향 데이터 바인딩 구현 시간 단축
컴포넌트에서 v-model 사용 방법
1. 기본 v-model 구현
- props:
modelValue
라는 이름의 props를 정의합니다. - 이벤트: 값이 변경될 때
update:modelValue
이벤트를 발생시킵니다.
<**CustomInput** v-model="username" />
<!-- 내부적으로 아래와 같이 변환됨-->
<CustomInput :modelValue="username" @update:modelValue="username = $event" />
2. 여러 v-model 바인딩
컴포넌트에 여러 개의 v-model을 사용할 수 있다.
<UserForm
v-model:firstName="userData.firstName"
v-model:lastName="userData.lastName"
v-model:agreeToTerms="userData.agreeToTerms"
/>
:firstName
prop과@update:firstName
이벤트:lastName
prop과@update:lastName
이벤트:agreeToTerms
prop과@update:agreeToTerms
이벤트
3. 수식어 처리
v-model에는 .trim
, .number
등의 수식어를 사용
커스텀 컴포넌트에서 이러한 수식어를 처리하려면 modelModifiers
prop을 사용한다.
5. provide, inject를 이용한 공용데이터 사용
props를 이용한 정보 전달은 컴포넌트의 계층 구조가 복잡해지면 계층 구조를 따라
연속적으로 속성을 전달해야하는 문제가 발생한다.
Provide
,Inject
: 컴포넌트 간 데이터를 전달하는 방법, 깊은 중첩 구조에서 props drilling 문제를 해결
Provide
: 상위 컴포넌트에서 데이터나 함수를 제공Inject
: 하위 컴포넌트에서 제공된 데이터나 함수를 주입- 장점: props를 여러 계층으로 전달할 필요 없이 직접 데이터를 공유할 수 있다
- 활용: 테마, 사용자 설정, 서비스 등 전역적으로 사용되는 데이터에 적합함.
Provide/Inject 활용 방법
- 반응형 데이터 전달
- 기본적으로 provide된 데이터는 반응형이 유지되지 않습니다.
- 반응형을 유지하려면 함수로 감싸거나 computed 속성을 사용하세요.
- 주요 사용 패턴
// 부모 컴포넌트에서
provide() {
return {
someData: this.someData, // 비반응형
getSomeData: () => this.someData, // 반응형 유지
someMethod: this.someMethod // 메소드 전달
}
}
// 자식 컴포넌트에서
inject: ['someData', 'getSomeData', 'someMethod']
- 기본값 설정
inject: {
theme: {
default: 'light' // 기본값 설정
}
}
Provide/Inject는 Vuex나 Pinia 같은 상태 관리 라이브러리를 사용하지 않고도 간단한 전역 상태를 관리할 수 있는 방법
특히 테마, 언어 설정, 사용자 인증 정보 등을 공유할 때 유용
6. 텔레포트(Teleport)
Teleport는 Vue 3에서 새롭게 도입된 기능
컴포넌트 템플릿의 일부를 DOM의 다른 위치로 "순간이동(teleport)"시킬 수 있게 한다.
기본 개념
- DOM 계층 분리: 컴포넌트의 논리적 계층과 DOM 계층 분리 가능
- CSS 제한 극복: z-index, overflow, position 등의 스타일 제한을 극복하는데 유용.
- to 속성: 대상 DOM 요소를 CSS 선택자로 지정.
- Teleport 내용은 부모 컴포넌트 상태에 접근 가능: 데이터 바인딩이 유지됨.
주요 사용 사례
- 모달 다이얼로그: 오버레이가 필요한 모달 창
- 툴팁: 부모 요소의 overflow 제한에서 벗어나야 하는 팝업
- 알림 메시지: 화면 상단에 표시되는 전역 알림
- 포털: 다른 DOM 노드로 내용을 포털링
활용 예시
1. 전역 알림 시스템
<teleport to="#notifications">
<div v-if="showNotification" class="notification">
{{ notificationMessage }}
</div>
</teleport>
2. 여러 대상으로 Teleport
<teleport to="#header">
<user-menu></user-menu>
</teleport>
<teleport to="#footer">
<copyright></copyright>
</teleport>
3. 동적 대상 지정
<teleport :to="teleportTarget">
<component-content />
</teleport>
주의사항
- 대상 요소(
to
속성에 지정한 요소)는 컴포넌트가 마운트되기 전에 DOM에 존재해야 한다. - Teleport는 내용을 다른 DOM 위치로 이동하지만, 컴포넌트 인스턴스 계층에는 영향을 주지 않는다.
- (부모 컴포넌트의 데이터와 메서드에 여전히 접근 가능).
- 여러 컴포넌트에서 같은 대상으로 Teleport할 경우, 내용이 순서대로 추가된다.
Teleport는 복잡한 레이아웃 문제를 간단하게 해결할 수 있게 해준다.
7. 마무리
컴포넌트 심화 파트는 실습을 진행하면서 건너뛰듯이 배운 파트이다.
그래서 제대로 된 설명이 부족하게 수업이 진행이 되었다. 사실 내용이 어렵고, 아직 잘 이해가 되지 않지만 대표 강사님이 요약하신 것을 토대로 정리하면서 조금은 이해할 수 있었다. 자바 학습이 끝나면 다시 돌아와서 보고 완벽하게 이해할 수 있게 되면 좋겠다.
오늘은 여기까지!
'Front-end > Vue.js' 카테고리의 다른 글
[Vue3]10_vue-router를 이용한 라우팅 1 (1) | 2025.04.22 |
---|---|
[Vue3] 09_Composition API (0) | 2025.04.20 |
[Vue3] 07_단일 컴포넌트 (2) | 2025.04.19 |
[Vue3] 06_스타일 적용 (0) | 2025.04.17 |
[Vue3] 05_이벤트 처리 (0) | 2025.04.13 |