[Vue3] 슬롯(Slot)

Dohee Kang·2023년 3월 4일
0

Vue

목록 보기
21/28
post-custom-banner

  • 슬롯은 템플릿 조각을 자식 컴포넌트에 전달하여 자식 컴포넌트가 자체 템플릿 내에서 조각을 렌더링하는 것이다.
<!-- 부모 템플릿 -->
<FancyButton>
  클릭하기! <!-- 슬롯 컨텐츠 -->
</FancyButton>

<!-- 자식 템플릿 -->
<button class="fancy-btn">
  <slot></slot> <!-- 슬롯 아울렛 -->
</button>

  • 최종 렌더링은 아래와 같이 된다.
<button class="fancy-btn">클릭하기!</button>

1. 이름이 있는 슬롯

  • name이라는 속성으로 컨텐츠가 렌더링되어야 하는 위치를 결정할 수 있다.
  • name 속성이 없다면 기본적으로 default란 이름을 갖는다.
<template>
  <div class="container">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>
  • 이름이 있는 슬롯을 전달하려면 부모 컴포넌트에서 <template> 엘리먼트와 함께 v-slot 디렉티브를 사용하고 슬롯명을 v-slot 인자로 전달해야 한다.
  • v-slot의 단축 문법은 #이 있으므로 <template #footer>처럼 단축하여 사용할 수 있다.
<BaseLayout>
  <template v-slot:header>
    <h1>Header</h1>
  </template>
  <template #default>
    <p>Main</p>
  </template>
  <template #footer>
    <p>Footer</p>
  </template>
</BaseLayout>

  • 컴포넌트가 기본 슬롯과 이름이 있는 슬롯을 모두 허용하는 경우, 최상위 비 <template> 노드는 기본 슬롯의 컨텐츠로 처리된다. 따라서 위의 예시는 아래처럼 작성할 수 있다.
<BaseLayout>
  <template v-slot:header>
    <h1>Header</h1>
  </template>
  
  <!-- default slot -->
  <p>Main</p>
  
  <template #footer>
    <p>Footer</p>
  </template>
</BaseLayout>
  • 최종 렌더링된 HTML 결과는 아래와 같다.

2. 동적인 슬롯 이름

  • 동적인 디렉티브 인자는 v-slot에서도 작동한다.
<BaseLayout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>

  <!-- 단축 문법 사용 -->
  <template #[dynamicSlotName]>
    ...
  </template>
</BaseLayout>

3. 범위가 지정된 슬롯

  • 슬롯 컨텐츠는 부모 컴포넌트에 정의되어 있으므로 부모 컴포넌트의 데이터 범위에 접근할 수 있다.
<!-- App.vue -->
<template>
  <!-- 모두 정상적으로 출력된다. -->
  <p>{{ firstName }}</p>
  <MyComponent>{{ firstName }}</MyComponent>
</template>

<script>
import MyComponent from './components/MyComponent.vue';

export default {
  ...
  data() {
    return {
      firstName: 'Dohee'
    }
  }
}
</script>
  • 다음과 같이 상위 컴포넌트에서 선언된 슬롯 컨텐츠에서 자식 컴포넌트(하위) 범위 내에 있는 데이터를 사용할 수 없다.
<!-- App.vue -->
<template>
  <!-- 출력되지 않음 -->
  <MyComponent>{{ greetingMsg }}</MyComponent>
</template>

<!-- MyComponent.vue -->
<script>
export default {
  data() {
    return {
      greetingMsg: '안녕',
      count: 1
    }
  }
}
</script>
  • 아래 코드처럼 작성하면 자식 컴포넌트(하위) 범위 내에 있는 데이터를 상위 컴포넌트에서 선언된 슬롯 컨텐츠에서 사용할 수 있다.
    • <slot :text="greetingMsg" :count="count" />
      • 속성을 부모 컴포넌트에 작성된 슬롯 컨텐츠에 전달
    • <MyComponent v-slot="slotProps">
      • 단일 기본 슬롯을 사용할 때와 달리 v-slot을 통해 슬롯 props를 받는다.
    • <p>{{ slotProps.text }} {{ slotProps.count }}</p>
      • 슬롯 범위 내에서 슬롯 props를 통해 값을 사용할 수 있다.
<!-- App.vue -->
<template>
  <MyComponent v-slot="slotProps">
    <p>{{ slotProps.text }} {{ slotProps.count }}</p>
  </MyComponent>
</template>

<!-- MyComponent.vue -->
<template>
  <div>
    <slot :text="greetingMsg" :count="count" />
    <button type="button" @click="count++">Click</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      greetingMsg: '안녕',
      count: 1
    }
  }
}
</script>

  • v-slot은 분해 할당하여 사용할 수 있다.
<MyComponent v-slot="{ text, count }">
  {{ text }} {{ count }}
</MyComponent>
  • 이름이 있고 범위가 지정된 슬롯은 아래와 같이 사용할 수 있다.
<MyComponent>
  <template #header="headerProps">
    {{ headerProps }}
  </template>

  <template #default="defaultProps">
    {{ defaultProps }}
  </template>

  <template #footer="footerProps">
    {{ footerProps }}
  </template>
</MyComponent>
  • 명명된 슬롯과 기본 범위 슬롯을 혼합하여 컴포넌트에 v-slot 지시어를 사용하면 컴파일 에러가 발생한다.
    • 기본 슬롯의 props 범위에 대한 모호함을 피하기 위한 에러
<template>
  <MyComponent v-slot="{ message }">
    <p>{{ message }}</p>
    <template #footer>
      <p>{{ message }}</p>
    </template>
  </MyComponent>
</template>
  • 기본 슬롯에 명시적인 <template>을 사용하여 v-slot 사용을 피해 다른 슬롯에서 message prop을 사용할 수 없음을 명확히 알 수 있다.
<template>
  <MyComponent>
    <!-- Use explicit default slot -->
    <template #default="{ message }">
      <p>{{ message }}</p>
    </template>

    <template #footer>
      <p>Here's some contact info</p>
    </template>
  </MyComponent>
</template>
profile
오늘은 나에게 어떤 일이 생길까 ✨
post-custom-banner

0개의 댓글