Vue 슬롯(slot) 사용법 및 예제 (Understanding Slot in Vue.js)

남양주개발자

·

2021. 11. 2. 08:19

728x90
반응형

Vue 슬롯(slot) 사용법 및 예제 (Understanding Slot in Vue.js)

Vue.js에서 컴포넌트를 활용해서 코드를 재사용함으로써 업무 효율성 및 생산성을 향상시킬 수 있습니다. 추가적으로 컴포넌트에 슬롯(Slots)을 활용하면 훨씬 재사용하기 용이하게 만들 수 있습니다. 이번 포스팅은 이미 컴포넌트 기초를 이해하고 있다 가정하고 작성한 내용이기 때문에 혹시 컴포넌트라는 개념을 아직 모르신다면 오늘 소개할 슬롯(Slots) 패턴을 이해하기 조금 까다로울 수 있습니다.

🤔 슬롯(Slots)이란?

일반적으로 슬롯이라고 하면 1) (무엇을 집어넣도록 만든 가느다란) 구멍2) (가느다란 구멍·자리에) 넣다 라는 뜻을 가지고 있는데 Vue 개발팀도 작명하는데 꽤 고심했다는 것이 이런 부분에서 느껴집니다. 정말 간단하게 표현하자면, 슬롯이란 컴포넌트에 콘텐츠나 다른 컴포넌트를 또 다른 방식으로 주입시킬 수 있는 방법입니다.

슬롯(Slots)은 엄격한 부모(Parent)-자식(Child) 컴포넌트 관계가 아닌 다른 방식으로 컴포넌트를 구성할 수 있는 Vue 컴포넌트 메커니즘입니다. 슬롯은 콘텐츠를 새로운 위치에 배치하거나 컴포넌트를 보다 일반적으로 만들 수 있는 방식을 제공합니다. 서론에서 잠깐 언급했듯이, 이러한 메커니즘을 통해 컴포넌트를 훨씬 재사용하기 용이하게 만듭니다. 사실 글로 이해하기 보다는 코드를 통해 이해하는 것이 훨씬 쉬울 것입니다.

💻 기본 사용 예시

슬롯을 활용해 콘텐츠 삽입하기

Vue에 있는 컨텐트 배포 API는 <slot> 요소를 컨텐트 배포 통로로 사용하는 Web Components spec draft에서 영향을 받았다고 합니다. 아래 코드 예시와 같이 부모 컴포넌트와 자식 컴포넌트를 만들 수 있습니다. 부모컴포넌트에서 Child 컴포넌트를 추가하고, Child 컴포넌트에 "slot content"라는 콘텐츠를 삽입합니다. Child 컴포넌트 내부에는 부모에서 전달하는 삽입된 값을 받기 위해 <slot></slot>을 추가합니다.

// Parent Component
<template>
  <div id="app">
    <Child>slot content</Child>
  </div>
</template>

<script>
import Child from "./components/Child";

export default {
  name: "App",
  components: {
    Child,
  },
};
</script>
<template>
  <div class="child">
    <slot></slot>
  </div>
</template>

컴포넌트가 렌더링될 때 <slot></slot>이 부모 컴포넌트에서 전달받은 "slot content"로 교체되는 것을 확인할 수 있습니다. 슬롯에는 HTML 같은 템플릿 코드를 포함할 수 있기 때문입니다.

정상적으로 슬롯에 콘텐츠가 삽입된 예시

슬롯에 다른 컴포넌트를 삽입하는 것도 가능합니다! 아래 예시는 Hello 컴포넌트를 Child 컴포넌트 슬롯에 전달합니다.

<template>
  <div id="app">
    <Child>
      <Hello />
    </Child>
  </div>
</template>

<script>
import Child from "./components/Child";
import Hello from "./components/Hello";

export default {
  name: "App",
  components: {
    Child,
    Hello,
  },
};
</script>

만약 <Child> 컴포넌트에 <slot> 요소를 가지고 있지 않다면 그 자리에 들어갔어야 할 모든 내용은 모두 무시됩니다.

기본값 지정(Fallback Content)

슬롯에 아무 컨텐트도 전달되지 않았을 때 슬롯에 렌더링시킬 기본값을 지정할 수 있습니다.

// Parent Component
<template>
  <div id="app">
    <Child />
  </div>
</template>

// Child Component
<template>
  <div class="child">
    <div>
      <slot>default value</slot>
    </div>
  </div>
</template>

Child 컴포넌트 슬롯을 비워놓고 렌더링했을 경우 아래와 같이 default value가 화면에 렌더링되는 것을 확인할 수 있습니다. 그 이유는 Child 컴포넌트 <slot> 요소 안에 "default value" 콘텐츠를 기본값으로 넣어줬기 때문입니다.

슬롯(Slot) 요소에 기본값을 설정한 후 화면에 렌더링한 예시

만약 슬롯에 콘텐츠를 전달하면, 기본값 대신 전달받은 콘텐츠를 렌더링하게 됩니다!

// Parent Component
<template>
  <div id="app">
    <Child>parent content</Child>
  </div>
</template>

이름이 있는 슬롯(Named Slots)

슬롯에도 이름을 지정함으로써 여러 개의 슬롯을 활용할 수 있습니다. 예를 들어 <base-layout> 컴포넌트의 아래 템플릿을 봅시다.

<div class="container">
  <header>
    <!-- 헤더는 여기에 넣을 겁니다 -->
  </header>
  <main>
    <!-- 본문은 여기에 넣을 겁니다 -->
  </main>
  <footer>
    <!-- 푸터는 여기에 넣을 겁니다 -->
  </footer>
</div>

이런 경우를 위해서 <slot> 요소는 서로 다른 슬롯들을 정의할 때 사용할 수 있는 name이라는 특별한 속성을 가지고 있습니다. 사용법은 슬롯 태그에 name 속성에 원하는 이름을 추가하면 끝입니다. 정말 간단하죠? 

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

name이 지정되지 않은 <slot>에는 암묵적으로 "default" 값이 사용됩니다. 이름이 있는 슬롯에 내용을 전달하려면 <template>에 v-slot 디렉티브를 쓰고 그 속성에 앞에서 지정한 ‘name’을 넣으면 됩니다.

<base-layout>
  <template v-slot:header>
    <h1>페이지 타이틀!</h1>
  </template>

  <p>name이 지정되지 않은 <slot>에는 암묵적으로 "default"</p>

  <template v-slot:footer>
    <p>페이지 푸터!</p>
  </template>
</base-layout>

참고로 2.6.0+ 버전부터 이름이 있는 슬롯 디렉티브에 단축표기(Shorthand)도 가능합니다. 인수 앞에 쓰는 부분(v-slot:)을 특수 기호인 #으로 대체하는 것입니다. 예를 들어 v-slot:header는 #header로 쓸 수도 있습니다. 단축 표기를 쓰려면 반드시 슬롯의 이름을 작성해야된다는 점 유의하세요!

<base-layout>
  <template #header>
    <h1>페이지 타이틀!</h1>
  </template>

  <p>name이 지정되지 않은 <slot>에는 암묵적으로 "default"</p>

  <template #footer>
    <p>페이지 푸터!</p>
  </template>
</base-layout>

컴파일될 때의 범위(Compilation Scope)

슬롯 안에 데이터를 활용하고 싶을 경우가 생깁니다. 아래 예시를 봅시다.

// Child 컴포넌트에 name 속성 값을 전달하고, 슬롯에 name을 사용하면 undefined를 리턴할 것입니다.
<Child userName="doodoo"> my name is {{ userName }} </Child>

슬롯에 추가한 콘텐츠는 컴파일 되기 전에 부모 컴포넌트 범위(Scope)에 있기 때문에 userName이라는 속성 값에 접근할 수 없습니다. 부모 템플릿 안에 있는 것들은 부모 컴포넌트의 범위에 컴파일되고 자식 템플릿 안에 있는 것들은 자식 컴포넌트의 범위에 컴파일됩니다.

범위가 있는 슬롯(Scoped Slots)

앞에서 언급했듯이 부모 템플릿 안에 있는 것들은 부모 컴포넌트의 범위에 컴파일되고 자식 템플릿 안에 있는 것들은 자식 컴포넌트의 범위에 컴파일 된다고 이야기했습니다. 하지만 어떨 때는 자식 컴포넌트에서만 접근할 수 있는 데이터에서 슬롯에 필요한 내용을 가져와야 할 수 있습니다. 아래 템플릿의 <Child> 컴포넌트의 예를 살펴봅시다.

// Parent Component
<template>
  <div id="app">
    <Child userName="kyungdoo"></Child>
  </div>
</template>

// Child Component
<template>
  <div class="child">
    <div>
      <slot>{{ userName }}</slot>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    userName: {
      type: String,
      default: "",
    },
    age: {
      type: Number,
      default: 0,
    },
  },
};
</script>

아래와 같이 기본값을 유저이름(userName) 값 말고 나이(age)와 조합한 다른 콘텐츠로 변경하고 싶을 수 있습니다.

<Child userName="kyungdoo" age="31">
  <template> {{ userName }}, {{ age }} </template>
</Child>

하지만 이 파일은 작동하지 않습니다. 왜냐하면 <Child> 컴포넌트만 name과 age 값에 접근할 수 있는데 슬롯에 제공되는 내용들은 부모 컴포넌트에서 렌더링되기 때문입니다. 부모 컴포넌트의 슬롯에서 해당 속성 값들을 사용하려면  userName, age 속성값을 <slot> 요소에 속성으로 연결해야 합니다.

<slot> 요소에 연결된 속성을 **슬롯 속성(slot props)**라고 합니다. 이제 부모 컴포넌트의 범위(scope)에서 v-slot에 연결한 ‘슬롯 속성(slotProps)’를 쓸 수 있습니다.

// Child Component
// <slot> 요소에 props를 전달한다.
<template>
  <div class="child">
    <div>
      <slot :userName="userName" :age="age">{{ userName }}</slot>
    </div>
  </div>
</template>

// Parent Component
// v-slot을 활용해 props를 전달받아 부모 컴포넌트에서 사용한다.
<Child userName="kyungdoo" :age="31">
  <template v-slot:default="slotProps">{{ slotProps }}</template>
</Child>

‘슬롯 속성’의 이름을 "slotProps"라고 썼는데 이 이름은 당연히 사용자가 원하는 대로 바꿀 수 있습니다.

슬롯 속성 구조분해(Destructuring Slot Props)

ES2015 구조분해 패턴을 활용하면 훨씬 깔끔하게 슬롯 속성 값을 가져올 수 있습니다.

// Parent Component (구조분해 전)
<Child userName="kyungdoo" :age="31">
  <template v-slot:default="slotProps">{{ slotProps }}</template>
</Child>

// Parent Component (구조분해 후)
<Child userName="kyungdoo" :age="31">
  <template v-slot:default="{ userName, age }">{{ userName }} {{age}}</template>
</Child>

이를 통해서 템플릿을 더 깨끗하게 만들 수 있습니다. 특히 슬롯에서 다양한 슬롯 속성들을 사용할 경우에 그렇습니다. 또 다른 기능으로 속성의 이름도 재정의할 수 있습니다.

<Child userName="kyungdoo" :age="31">
  <template v-slot:default="{ userName: name, age }">{{ name }} {{age}}</template>
</Child>

속성이 undefined이면 슬롯에 들어갈 기본값을 정할 수도 있습니다.

<Child userName="kyungdoo" :age="31">
  <template v-slot:default="{ bye = 'hello' }">
     {{ bye }}
   </template>
</Child>

 

728x90
반응형
그리드형

이 포스팅은 쿠팡파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

💖 저자에게 암호화폐로 후원하기 💖

아이콘을 클릭하면 지갑 주소가자동으로 복사됩니다