Vue 컴포넌트에는 멋진 기능이 하나 더 있다.
이걸 사용하면 코드를 구성하고 여러 컴포넌트로 분할할 때 훨씬 더 많은 옵션을 제공한다. → 바로 slot 기능
# slot은 뭘까?
UserInfo.vue에는 header와 p가 포함된 section이 있다.
<!--UserInfo.vue-->
<template>
<section>
<header>
<h3>{{ fullName }}</h3>
<base-badge :type="role" :caption="role.toUpperCase()"></base-badge>
</header>
<p>{{ infoText }}</p>
</section>
</template>
<style scoped>
section {
margin: 2rem auto;
max-width: 30rem;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
padding: 1rem;
}
/*이하 생략*/
</style>
BadgeList.vue에도 컨텐츠가 포함된 section이 있다.
<template>
<section>
<h2>Available Badges</h2>
<ul>
<li>
<base-badge type="admin" caption="ADMIN"></base-badge>
</li>
<li>
<base-badge type="author" caption="AUTHOR"></base-badge>
</li>
</ul>
</section>
</template>
<style scoped>
section {
margin: 2rem auto;
max-width: 30rem;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
padding: 1rem;
}
/*이하 생략*/
</style>
BadgeList.vue와 UserInfo.vue의 컨텐츠는 서로 다르지만, 두 컴포넌트에 공통으로 포함된 것이 있다.
바로 section태그 + section에 적용된 스타일링.
두 컴포넌트에 있는 section이 같은 스타일을 가지고 있다.
이를 위해 두가지 대안이 있다.
1/ 스타일 범위가 지정되지 않은 App.vue에서 section을 전역 스타일로 만들 수 있다.
2/ section과 여기에 적용된 스타일링을 가진 독립형 컴포넌트를 만드는 것. 그러면 컨텐츠를 유연하게 수신할 수 있게 된다.
BaseCard.vue라는 컴포넌트를 하나 만들어보자.
웹에서 Card look으로 불리는 스타일이 있다. 아래와 같이 그림자 효과와 둥근 모서리를 가진 스타일을 card look이라고 한다.
BaseCard.vue에 이 스타일을 캡슐화하는 컴포넌트를 생성해 어디에서나 쉽게 재사용할 수 있도록 해보자.
BaseCard.vue 컴포넌트에서 중요한 건 scoped 속성을 추가해서 스타일의 범위를 지정하는 것.
이 컴포넌트는 특정 스타일이 적용된 wrapper로 사용한다.
그렇다면 이 컴포넌트는 어떻게 외부로부터 컨텐츠를 수신할 수 있을까? → 앞서 배운 props가 있다.
그렇다면 어떤 것이 props가 될까? props에 'content'를 넣은 다음 template에 content를 출력하면 될까?
<!--BaseCard.vue-->
<template>
<div>{{ content }}</div>
</template>
<script>
export default {
props: ["content"],
};
</script>
위 코드는 아쉽게도 Vue기능이 포함된 HTML 콘텐츠를 전달할 수 없다.
그럼 UserInfo.vue에서 전달할 컨텐츠를 <base-card>로 wrapping해보자.
<!--기존 UserInfo.vue-->
<template>
<section>
<header>
<h3>{{ fullName }}</h3>
<base-badge :type="role" :caption="role.toUpperCase()"></base-badge>
</header>
<p>{{ infoText }}</p>
</section>
</template>
▼
<!--wrapping한 UserInfo.vue-->
<template>
<section>
<base-card>
<header>
<h3>{{ fullName }}</h3>
<base-badge :type="role" :caption="role.toUpperCase()"></base-badge>
</header>
<p>{{ infoText }}</p>
</base-card>
</section>
</template>
section을 교체하는 것이 아니라, base-card를 section 안에 넣는다. 그리고 컨텐츠를 base-card로 wrapping한다. 하지만 이건 props가 작동하는 방식이 아니다.
위와 같이 코드를 작성하면, UserInfo.vue 컴포넌트의 모든 컨텐츠가 사라진다. 왜냐하면 Vue가 UserInfo.vue 컴포넌트에 있는 <base-card> 태그로 감싸진 컨텐츠의 렌더링할 위치를 모르기 때문이다.
- 커스텀 컴포넌트(BaseCard.vue)의 태그 사이에 추가했는데 이것들로 무엇을 할지, 어디에 출력할지 모른다.
BaseCard.vue 컴포넌트에는 자체 템플릿이 있기도 하고, content 프로퍼티가 있지만 Vue는 그 안에 무엇이 있는지 모른다.
base-card 태그에 content="" 이렇게 설정하고, header이하 부분을 문자열로 content에 전달할 수 있지만, 그렇게 하면 더이상 header 이하 부분은 Vue 기능을 사용할 수 없게된다.
<base-card content="<header><h2>{{fullName}}..."></base-card>
전부 해결책이 될 수 없다.
## slot 사용
대신, 언급했던 slot기능을 사용할 수 있다.
- Vue에는 바로 이런 상황에서 활용할 수 있는 특수 구문이 있다. 컴포넌트를 동적 콘텐츠, 즉 다른 HTML 컨텐츠의 wrapper로 사용하는 경우.
- wrapper로 사용될 컴포넌트로 이동해서 props대신 특수 요소인 slot을 사용하자. 그리고 UserInfo.vue 컴포넌트에서는 section 스타일을 삭제하자.
<!--BaseCard.vue-->
<template>
<div>
<slot></slot>
</div>
</template>
이제 잘 작동한다. 원하는 스타일도 적용됐다.
UserInfo.vue 컴포넌트에서 section 스타일을 삭제한 사실이 중요하다. card look 스타일은 BaseCard.vue 컴포넌트에서 가져온 것.
이것이 slot이 작동하는 방식이다.
# slot의 기능
- Vue 기능을 사용할 수 있는 HTML 콘텐츠를 외부 컴포넌트로부터 수신할 수 있게 해준다.
- 기본적으로 props와 같지만, props는 컴포넌트가 필요로하는 'data'에 사용되고,
- slot은 컴포넌트에 필요한 템플릿 코드의 HTML 코드에 사용된다.
위 예시에서는 스타일링이 적용된 shell을 제공하고 있다.
필요하다면 컴포넌트에 로직을 추가할 수도 있지만, 우리는 부모 컴포넌트로부터 div 내부에 있어야 하는 HTML 코드를 전달받는다. 이것이 바로 slot의 핵심 취지.
** 출처: 모든 내용은 Udemy Vue-완벽가이드 강의를 기반으로 작성하였습니다.
'Vue.js' 카테고리의 다른 글
[Udemy Vue 완벽가이드 Section9] 115. 슬롯 스타일 및 컴파일 (0) | 2023.08.30 |
---|---|
[Udemy Vue 완벽가이드 Section9] 114. 이름이 있는 슬롯(Named Slots) (0) | 2023.08.29 |
[Udemy Vue 완벽가이드 Section9] 112. 범위가 지정된(scoped) 스타일 (0) | 2023.08.29 |
[Udemy Vue 완벽가이드 Section9] 111. 전역 컴포넌트와 지역 컴포넌트 (0) | 2023.08.29 |
[Udemy Vue 완벽가이드 Section9] 109. 섹션 소개 (0) | 2023.08.29 |