본문 바로가기

Vue.js

[Udemy Vue 완벽가이드 Section9] 113. 슬롯(slots) 소개

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-완벽가이드 강의를 기반으로 작성하였습니다.