이번 포스트에서는 컴포넌트가 자식 요소를 가질 수 있도록 기능을 제공하는 slot을 살펴보도록 하겠습니다.
Slot
HTML 요소는 아래와 같이 자식 요소를 가질 수 있습니다.
<div>
<p>I'm a child of the div</p>
</div>
컴포넌트도 slot을 사용하면 자식 요소를 가질 수 있게 됩니다. Vue.js와 Svelte의 slot 사용 방법은 비슷합니다.
Vue.js 예제 코드
Vue.js에서는 아래 코드와 같이 <slot>을 사용할 수 있습니다.
<!-- Box.vue -->
<template>
<div class="box">
<slot></slot>
</div>
</template>
<!-- App.vue -->
<template>
<Box>
<h2>Hello!</h2>
<p>This is a box. It can contain anything.</p>
</Box>
</template>
<script>
import Box from './Box'
export default {
components: {
Box
}
}
</script>
Svelte 예제 코드
Svelte에서는 아래 코드와 같이 <slot>을 사용할 수 있습니다.
<!-- Box.svelte -->
<div class="box">
<slot></slot>
</div>
<!-- App.svelte -->
<script>
import Box from './Box.svelte';
</script>
<Box>
<h2>Hello!</h2>
<p>This is a box. It can contain anything.</p>
</Box>
App.svelte에 <Box>의 자식 요소들이 Box.svelte에 <slot> 요소의 위치에 나타나게 됩니다.
Default slot
컴포넌트의 자식 요소가 정의되어 있지 않은 경우 기본 요소를 정의할 수 있습니다. Vue.js와 Svelte의 slot 기본값을 사용 방법은 비슷합니다.
Vue.js 예제 코드
Vue.js에서 slot의 기본값 사용 방법은 아래와 같습니다.
<!-- Box.vue -->
<template>
<div class="box">
<slot>
<em>no content was provided</em>
</slot>
</div>
</template>
<!-- App.vue -->
<template>
<Box>
<h2>Hello!</h2>
<p>This is a box. It can contain anything.</p>
</Box>
<Box/>
</template>
<script>
import Box from './Box'
export default {
components: {
Box
}
}
</script>
Svelte 예제 코드
Svelte에서 slot의 기본값 사용 방법은 아래와 같습니다.
<!-- Box.svelte -->
<div class="box">
<slot>
<em>no content was provided</em>
</slot>
</div>
<script>
import Box from './Box.svelte';
</script>
<Box>
<h2>Hello!</h2>
<p>This is a box. It can contain anything.</p>
</Box>
<Box/>
App.svelte에 <Box>의 자식 요소가 정의되어 있지 않을 경우, Box.svelte에 <slot> 태그 안에 요소가 화면에 출력됩니다.
Named slot
이름이 있는 slot을 만들 수 있습니다. 이름이 있는 slot 역시 Vue.js와 Svelte의 사용법이 비슷합니다.
Vue.js 예제 코드
Vue.js에서 아래 코드와 같이 이름이 있는 slot을 사용할 수 있습니다.
<!-- ContactCard.vue -->
<template>
<article class="contact-card">
<h2>
<slot name="name">
<span class="missing">Unknown name</span>
</slot>
</h2>
<div class="address">
<slot name="address">
<span class="missing">Unknown address</span>
</slot>
</div>
<div class="email">
<slot name="email">
<span class="missing">Unknown email</span>
</slot>
</div>
</article>
</template>
<!-- App.vue -->
<template>
<ContactCard>
<span slot="name">
P. Sherman
</span>
<span slot="address">
42 Wallaby Way<br>
Sydney
</span>
</ContactCard>
</template>
<script>
import ContactCard from './ContactCard.svelte';
export default {
components: {
Box
}
}
</script>
Svelte 예제 코드
Svelte에서 아래 코드와 같이 이름이 있는 slot을 사용할 수 있습니다.
<!-- ContactCard.svelte -->
<article class="contact-card">
<h2>
<slot name="name">
<span class="missing">Unknown name</span>
</slot>
</h2>
<div class="address">
<slot name="address">
<span class="missing">Unknown address</span>
</slot>
</div>
<div class="email">
<slot name="email">
<span class="missing">Unknown email</span>
</slot>
</div>
</article>
<!-- App.svelte -->
<script>
import ContactCard from './ContactCard.svelte';
</script>
<ContactCard>
<span slot="name">
P. Sherman
</span>
<span slot="address">
42 Wallaby Way<br>
Sydney
</span>
</ContactCard>
이름이 있는 Slot을 사용하면 원하는 위치에 요소를 배치할 수 있습니다. 위의 코드를 살펴보면,
ContactCard.svelte에는<slot name="name">,<slot name="address">,<slot name="email">3개의 Slot이 정의되어 있습니다.App.svelte에는slot="name"과slot="address"를 사용하여ContactCard.svelte의<slot name="name">,<slot name="address">가 정의된 위치에 나타납니다.App.svelte에slot="email"을 사용하지 않았기 때문에ContactCard.svelte의<slot name="email">은 기본 요소가 나타내게 됩니다.
Slot props
slot의 데이터를 상위 컴포넌트로 전달하는 방법을 살펴보겠습니다.
Vue.js 예제 코드
Vue.js에서는 아래와 같이 데이터를 전달할 수 있습니다.
<!-- Hoverable.vue -->
<template>
<div @mouseenter="enter" @mouseleave="leave">
<slot :hovering="hovering"></slot>
</div>
</template>
<script>
export default {
data () {
return {
hovering: false
}
},
methods: {
enter () {
this.hovering = true
},
leave () {
this.hovering = false
}
}
}
</script>
<!-- App.vue -->
<template>
<div>
<Hoverable>
<template v-slot="slotProps">
<div :class="{active : slotProps.hovering }">
<p v-if="slotProps.hovering ">I am being hovered upon.</p>
<p v-else>Hover over me!</p>
</div>
</template>
</Hoverable>
<Hoverable>
<template v-slot="slotProps">
<div :class="{active : slotProps.hovering }">
<p v-if="slotProps.hovering ">I am being hovered upon.</p>
<p v-else>Hover over me!</p>
</div>
</template>
</Hoverable>
</div>
</template>
<script>
import Hoverable from 'Hoverable'
export default {
components: {
Hoverable
}
}
</script>
<style>
div {
padding: 1em;
margin: 0 0 1em 0;
background-color: #eee;
}
.active {
background-color: #ff3e00;
color: white;
}
</style>
Svelte 예제 코드
Svelte에서는 아래와 같이 데이터를 전달할 수 있습니다.
<!-- Hoverable.svelte -->
<script>
let hovering;
function enter() {
hovering = true;
}
function leave() {
hovering = false;
}
</script>
<div on:mouseenter={enter} on:mouseleave={leave}>
<slot hovering={hovering}></slot>
</div>
<!-- App.svelte -->
<script>
import Hoverable from './Hoverable.svelte';
</script>
<style>
div {
padding: 1em;
margin: 0 0 1em 0;
background-color: #eee;
}
.active {
background-color: #ff3e00;
color: white;
}
</style>
<Hoverable let:hovering={active}>
<div class:active>
{#if active}
<p>I am being hovered upon.</p>
{:else}
<p>Hover over me!</p>
{/if}
</div>
</Hoverable>
<Hoverable let:hovering={active}>
<div class:active>
{#if active}
<p>I am being hovered upon.</p>
{:else}
<p>Hover over me!</p>
{/if}
</div>
</Hoverable>
Hoverable.svelte
Hoverable.svelte에 정의된 slot에 <slot hovering={hovering}></slot>와 같이 상위 컴포넌트로 전달할 값을 선언해 줍니다. hovering은 상위로 전달할 변수 명이고, {hovering}은 상위로 전달할 변수입니다. 두 개의 이름이 hovering으로 동일하기 때문에 <slot {hovering}></slot>와 같은 약어로 작성할 수 있습니다.
App.svelte
App.svelte에 <Hoverable let:hovering={active}>와 같이 let 디렉티브를 사용해서 값을 전달받을 수 있습니다. let:hovering의 hovering은 Hoverable.svelte의 <slot hovering={...}>에 <slot> 태그의 속성 이름, hovering과 동일해야 합니다.
이렇게 전달된 값은 로컬 방식으로 동작하기 때문에 별도의 변수를 정의하지 않아도 됩니다. 위의 예제의 active는 변수를 정의하지 않았습니다. 또한 active는 컴포넌트의 하위 요소에만 사용할 수 있습니다. 위의 예제에 두 개의 Hoverable 컴포넌트를 사용했는데, active는 각각의 컴포넌트 안에서 독립적으로 동작합니다.