-
Notifications
You must be signed in to change notification settings - Fork 0
섹션 9: 컴포넌트 자세히 알아보기
component를 메서드를 이용하여 앱에 컴포넌트 등록
// main.js
import TheHeader from './components/TheHeader.vue';
app.component('the-header', TheHeader);
방법적으로는 문제가 없지만 가장 좋은 방법은 아니다.
component를 메서드를 이용하여 앱에 컴포넌트를 등록하면
전역(global) 컴포넌트로 등록된다.(Vue 앱 어느 곳에서나, 즉 어느 템플릿에서나 사용할 수 있는 컴포넌트)
main.js의 import, component 메서드 제거 후
App.vue 파일의 script 섹션에 import/components 프로퍼티 추가
// App.js
<script>
import TheHeader from "./components/TheHeader.vue";
export default {
components: {
"the-header": TheHeader,
// TheHeader: TheHeader,
// TheHeader
},
data() {
return {
};
},
};
</script>
// App.vue
<template>
<the-header></the-header>
<!-- <TheHeader /> -->
</template>
이제 App.vue의 템플릿 또는 컴포넌트에서 어느 컴포넌트가 사용될지를 Vue에 알릴 수 있다.
그리고 여기에 등록된 컴포넌트는 App.vue에서만 사용할 수 있다.
components는 객체를 취하는데 키-값 쌍이 필요하다.
키는 커스텀 HTML 요소이다.(태그)
값은 불러온(import) 컴포넌트 구성 객체인데 포인터로 가리키고 있다.
앱 전체에 영향을 미치는 스타일은 일반적으로 App.vue(전체 애플리케이션의 진입) 파일의 style 태그에서 정의된다.
하지만 개별 컴포넌트에 적용할 스타일이 있는 경우에는 scoped
속성을 이용해
정의된 스타일이 같은 파일 내에 있는 템플릿에만 적용되도록 한다.
vue가 요소의 선택자를 변경한다.
모든 컴포넌트의 템플릿은 고유한 속성을 받기 때문에 스타일링의 범위가 해당 컴포넌트의 텝플릿 마크업으로 지정된다.
자체 컴포넌트를 동적 콘텐츠. 즉, 다른 HTML 콘텐츠의 래퍼로 사용하는 경우
코드를 구성하고 여러 컴포넌트로 분할할 때 훨씬 더 많은 옵션을 제공
Vue 기능을 사용할 수도 있는 HTML 콘텐츠를 외부 컴포넌트로부터 수신할 수 있게 해준다.
기본적으로 프로퍼티와 같지만 프로퍼티는 컴포넌트가 필요로 하는 데이터에 사용되고
슬롯은 컴포넌트에 필요한 템플릿 코드의 HTML 코드에 사용된다.
<template>
<div>
<slot></slot>
</div>
</template>
<style scoped>
div {
margin: 2rem auto;
max-width: 30rem;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
padding: 1rem;
}
</style>
// BaseCard.vue
<template>
<div>
<header>
<slot></slot>
</header>
<slot></slot>
</div>
</template>
이렇게 슬롯이 두 개 있으면 Vue는 제공된 콘텐츠가 어디로 가야하는지 알 수 없게 된다.
// BaseCard.vue
<template>
<div>
<header>
<slot name="header"></slot>
</header>
<slot></slot>
</div>
</template>
그래서 하나 이상의 슬롯을 사용하는 경우
슬롯 요소에 대한 name 속성을 사용해서 슬롯에 이름을 추가할 수 있다.
모든 슬롯의 이름을 지정할 필요는 없지만 이름이 없는 슬롯을 하나 남겨두면 그것이 기본 슬롯이 된다.
이름이 없는 슬롯은 하나만 있어야 한다.
// UserInfo.vue
<template>
<section>
<base-card>
<template v-slot:header>
<h3>{{ fullName }}</h3>
<base-badge :type="role" :caption="role.toUpperCase()"></base-badge>
</template>
<p>{{ infoText }}</p>
</base-card>
</section>
</template>
기본 HTML 요소인 template
태그로 래핑한 다음
name 슬롯에 들어갈 콘텐츠가 포함된 template 태그에
특수 디렉티브인 v-slot
디렉티브를 추가한다.
Vue에 특정 콘텐츠가 어디로 가야 할지를 알려줄 수 있다.(v-slot
에 인수 추가)
※ template
태그는 기본 HTML 태그로 화면에는 아무것도 렌더링 하지 않아 컨테이너로 사용
슬롯이 있는 템플릿 내부에 포함되지 않은 콘텐츠는 자동으로 기본 슬롯으로 이동한다.
<template>
<section>
<base-card>
<template v-slot:header>
<h3>{{ fullName }}</h3>
<base-badge :type="role" :caption="role.toUpperCase()"></base-badge>
</template>
<template v-slot:default>
<p>{{ infoText }}</p>
</template>
</base-card>
</section>
</template>
남아 있는 콘텐츠는 기본 슬롯으로 이동한다는 사실을 좀 더 분명하게 표시하기 위해
해당 콘텐츠 주의에 template를 추가한 다음 v-slot:default를 추가해준다.
(default는 예약어이다.)
콘텐츠를 슬롯에 할당하는 것을 잊은 것이 아니라 기본 슬롯으로 보낸다는 것을 분명히 한다.
// BaseCard.vue
<template>
<div>
<header>
<slot name="header">
<h2>기본</h2>
</slot>
</header>
<slot></slot>
</div>
</template>
// BadgeList.vue
<template>
<section>
<base-card>
<template v-slot:header>
<h2>Available Badges</h2>
</template>
<template v-slot:default>
(내용 생략)
</template>
</base-card>
</section>
</template>
BadgeList.vue에 <h2>Available Badges</h2>
가 있으므로 기본 제목 제공되지 않음
// BadgeList.vue
<template>
<section>
<base-card>
<template v-slot:default>
(내용 생략)
</template>
</base-card>
</section>
</template>
template 영역 없애니 기본 제목 노출됨
기본 폴백 콘텐츠를 렌더링하지 않으면 BadgeList.vue에는 제목이 표시되지 않는다.
문제 상황은 HTML 마크업을 살펴보면 DOM에 빈 header 요소가 있다.
header에 내용이 없음에도 <header></header>
가 출력중
$slots
를 사용해서 특정 슬롯에 대한 데이터를 수신하는지 확인하고
아니라면 해당 정보를 사용해서 특정 요소를 렌더링하지 않을 수 있을까?
Vue에서 제공하는 특수 내장 프로퍼티 $slots
슬롯을 정의하는 컴포넌트에서 mounted 수명 주기를 추가한 후 console.log(this.$slots)
입력
$slots
는 컴포넌트가 다른 슬롯에 대해 수신하는 슬롯 데이터에 관한 정보 보유
컴포넌트를 두 번 사용하고 있으니 console.log(this.$slots)
도 두 번 실행 중
이 객체에서 우리가 제공하는 다른 슬롯에 액세스 할 수 있다.
<template>
<div>
<header>
<slot name="header">
<!-- <h2>기본</h2> -->
</slot>
</header>
<slot></slot>
</div>
</template>
<script>
export default {
mounted() {
console.log("header", this.$slots.header);
console.log("default", this.$slots.default);
},
};
</script>
BadfgeList.vue 컴포넌트에서는 헤더 슬롯에 대한 콘텐츠를 제공하지 않았기 때문에
BaseCard.vue 컴포넌트에서 제공된 콘텐츠에 엑세스하려고 시도하면
BadgeList.vue에 사용될 BaseCard.vue 컴포넌트가 생성될 때 undefined가 출력
<template>
<div>
<header v-if="$slots.header">
<slot name="header">
<!-- <h2>기본</h2> -->
</slot>
</header>
<slot></slot>
</div>
</template>
header 요소에 v-if를 추가하고 $slots.header가 참인지 확인한다.
undefined가 나오면 거짓 → 따라서 이 header 요소는 렌더링되지 않는다.
<template v-slot:header>
↓
<template #header>
슬롯을 정의한 컴포넌트 내부에서 슬롯에 대한 마크업을 전달한 컴포넌트에 데이터를 전달할 수 있게 함.
슬롯을 정의한 컴포넌트에서 slot에 프로퍼티 추가
slotProps에 해당하는 값은 언제나 객체이다.
(슬롯을 정의한 컴포넌트에서 slot에서 정의한 모든 프로퍼티가 병합된 객체)
// CourseGoals.vue
<template>
<ul>
<li v-for="goal in goals" :key="goal">
<slot :item="goal" another-prop="test"></slot>
</li>
</ul>
</template>
<script>
export default {
data() {
return {
goals: ["수강 완료", "Vue 학습"],
};
},
};
</script>
// App.vue
<template>
<div>
<course-goals>
<template #default="slotProps">
<h2>{{ slotProps.item }}</h2>
<p>{{ slotProps.anotherProp }}</p>
</template>
</course-goals>
</div>
</template>
<script>
import CourseGoals from "./components/CourseGoals.vue";
export default {
components: {
CourseGoals,
},
};
</script>
default 슬롯과 같이 하나의 슬롯만을 대상으로 할 때 template 삭제 가능
// App.vue
<template>
<div>
<course-goals #default="slotProps">
<h2>{{ slotProps.item }}</h2>
<p>{{ slotProps.anotherProp }}</p>
</course-goals>
</div>
</template>
<script>
import CourseGoals from "./components/CourseGoals.vue";
export default {
components: {
CourseGoals,
},
};
</script>
버튼을 누르면 버튼에 해당하는 컴포넌트를 보여주고 싶을 때
v-if를 사용하여 구현할 수 있지만 이 방법은 번거롭다.
이 때 Vue에서 사용할 수 있는 동적 컴포넌트는 component 요소이다.
component 요소는 혼자서 동작하지 않고 is라는 키 프로퍼티가 필요하다.
<template>
<div>
<button @click="setSelectedComponenet('active-goals')">Active Goals</button>
<button @click="setSelectedComponenet('manage-goals')">Manage Goals</button>
<!--
<active-goals v-if="selectedComponent === 'active-goals'"></active-goals>
<manage-goals v-if="selectedComponent === 'manage-goals'"></manage-goals>
-->
<component :is="selectedComponent"></component>
</div>
</template>
<script>
import ActiveGoals from "./components/ActiveGoals.vue";
import ManageGoals from "./components/ManageGoals.vue";
export default {
components: {
ActiveGoals,
ManageGoals,
},
data() {
return {
selectedComponent: "active-goals",
};
},
methods: {
setSelectedComponenet(cmp) {
this.selectedComponent = cmp;
},
},
};
</script>
Manage Goals에 input 추가, 내용을 입력하고 Active Goals를 다녀오면 Manage Goals의 input에 입력된 텍스트가 사라진 것을 확인.
컴포넌트를 바꿀 때 이전 컴포넌트는 DOM에서 제거되기 때문
keep-alive는 컴포넌트를 완전히 제거하지 않고 이들의 상태를 내부에서 캐시로 저장하도록 Vue에게 알려준다.
<keep-alive>
<component :is="selectedComponent"></component>
</keep-alive>
텔레포느틑 컴포넌트의 계틍을 유지하면서 DOM의 구조를 조작하고
DOM에 추가되는 부분을 제어하기 위해 사용되는 내장 컴포넌트이다.
teleport는 to라는 한 가지 속성을 필요로 한다.
값으로는 CSS 선택자를 넣으면 된다.
<teleport to="body">
<error-alert v-if="inputIsInvalid">
<h2>Input is invalid!</h2>
<p>Please enter at least a few characters</p>
<button @click="confirmError">Okay</button>
</error-alert>
</teleport>
위 컴포넌트는 div를 제거한다고 가정할 경우
여러 개의 상위 레벨의 HTML 요소를 가진다.
Vue.js 버전2는 템플릿 하나에 루트 레벨 요소는 오직 하나만을 가져야했으나
Vue 3에서는 원하는 만큼 많은 상위 레벨의 요소를 가질 수 있다.
이 기능을 프래그먼트(Fragments)라고 부른다.
코드를 깨끗하고 이해하기 쉽도록 유지하기 위해 살펴본다.
공식 문서
기본 컴포넌트 이름
단일 인스턴스 컴포넌트의 경우(전체 앱에서 한 번만 사용/ex. TheHeader) 이름 앞에 The를 붙인다.