Svelte 기초 03 - slot
Tags: svelte, 프런트엔드, 스벨트, javascript
Published: 2022-07-20 13:28:17


# 1. slot이란?

이번에 알아볼 Svelte의 기능은 slot입니다. 

상태값과는 별개로 마크업 영역의 HTML이 반복적으로 사용될 때 재사용할 필요가 있는 경우도 종종 발생합니다. 이럴때 도움이 되는 기능이 바로 slot입니다. 

예를 들어 다음과 같은 Card 컴포넌트를 계속해서 재사용해야 할 일이 있다고 가정해 보겠습니다. 기본적인 레이아웃은 변하지 않지만, 마크업 영역의 HTML내용은 계속 변경되어 사용되는 상황입니다. 

넘겨지는 데이터의 경우 다음과 같은 h2, p 태그 안의 내용이 됩니다. 이와 같은 상황을 처리할 때 내용만을 props로 해서 전달하는 방법도 있습니다. 하지만 필요에 따라 컴포넌트 안에 사용되는 태그도 같이 변경된다면 어떨까요? 이런 상황을 props를 이용해 처리한다면 먼가 이상한 형태가 되고 말 것입니다.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i1s9wti9e0osk7ctle75.png)
 
 
자 그럼 slot을 어떻게 사용하면 되는지 간단한 예제로 알아보겠습니다. card.svelte라는 컴포넌트를 만들고 필요한 디자인 요소를 배치한 다음 card라는 div 영역을 만들어 주겠습니다.

다음으로 <slot></slot> 라는 영역을 만들어 주겠습니다.  이제 부터 해당 컴포넌트를 사용하는 곳에서 마크업을 작성하면 이 slot 안에 배치되게 됩니다. 

```jsx
<style>
	.card {
		width: 300px;
		border: 1px solid #aaa;
		border-radius: 2px;
		box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
		padding: 1em;
		margin: 0 0 1em 0;
	}
</style>

<div class="card">
	<slot></slot>
</div>
```

그럼 이렇게 만들어진 Card컴포넌트를 사용해 보겠습니다. App.svelte에서 이 컴포넌트를 불러와 주겠습니다. 

사용방법은 간단합니다. 말한것 처럼 다음과 같이 <card></card> 로 컴포넌트를 마크업으로 불러온 후

 그 사이에 원하는 다른 HTML로 이루어진 내용을 입력하면 됩니다. 즉 <slot></slot> 으로 정의된 부분에 HTML 태그를 사용할 수 있습니다.

이렇게 slot을 이용하면 card컴포넌트의 디자인요소(CSS)를 쉽게 재사용할 수 있게 되는 것입니다. 

```jsx
<script>
	import Card from './card.svelte';
</script>

<Card>
	<h2>안녕하세요!</h2>
	<p>이곳은 내용이 들어가는 영역입니다.</p>
</Card>


<Card>
	<h2>Hello Svelte</h2>
	<p>Svelte를 배우는 여루분을 환영합니다.</p>
	<p>즐거운 코딩 되세요...</p>
</Card>
```

코드를 실행해 보시면 다음과 같은 결과가 나옵니다.  

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o08gvamdjkkrfcyyh7hi.jpg)
 
Card라는 컴포넌트 안에 우리가 원하는 마크업을 다시 작성해서 사용할 수 있는 것입니다. 이제 slot을 대략 어떤 용도로 사용하는지 조금은 아셨을 것입니다.


# 2. slot  name 오션

이제 좀 더 실용적인 예제를 만들어 보도록 하겠습니다. 

다음과 같이 card.svelte 컴포넌트를 다시한번 만들어 보겠습니다. 우선 style 영역을 만들고 컴포넌트에 필요한 css를 추가하겠습니다. 이번에도 해당 css들은 더보기로 되어 링크에서 복사해 사용하시면 됩니다. 

그리고 마크업 영역에 <slot name="name">, <slot name="adress">, <slot name="name"> 의 세가지 slot 영역을 만들어 주겠습니다.

`card.svelte`
```jsx
<style>
.contact-card {
		width: 300px;
		border: 1px solid #aaa;
		border-radius: 2px;
		box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
		padding: 1em;
	}

	h2 {
		padding: 0 0 0.2em 0;
		margin: 0 0 1em 0;
		border-bottom: 1px solid #ff3e00
	}

	.address, .email {
		padding: 0 0 0 1.5em;
		background:  0 50% no-repeat;
		background-size: 1em 1em;
		margin: 0 0 0.5em 0;
		line-height: 1.2;
	}

	.address { background-image: url(tutorial/icons/map-marker.svg) }
	.email   { background-image: url(tutorial/icons/email.svg) }
	.missing { color: #999 }
</style>

<article class="contact-card">
	<h2>
		<slot name="name">
			<span class="missing">이름 미입력</span>
		</slot>
	</h2>

	<div class="address">
		<slot name="address">
			<span class="missing">주소 미입력</span>
		</slot>
	</div>

	<div class="email">
		<slot name="email">
			<span class="missing">이메일 미입력</span>
		</slot>
	</div>
</article>
```

이 컴포넌트는 name, addrss, email 이라는 3가지의 slot영역을 가지게 됩니다. 

이제 App 컴포넌트에서 name옵션을 이용해 slot을 사용하는 곳에서 명시적으로 배치할 slot을 지정할 수 있습니다. 이 card 컴포넌트를 이용해 앱을 실행해 보겠습니다.

`App.svelte`
```jsx
<script>
	import Card from './card.svelte';
</script>

<Card>
	<span slot="name">
		홍길동
	</span>

	<span slot="address">
		서울특별시<br>
		여의도동
	</span>
</Card>
```
실행에 시키시면  각 slot 에 필요한 내용이 채워진 것을 볼 수 있습니다. 그리고 사용하지 않은 slot의 경우는card.svelte에서 설정한 기본 값으로 표시된 것을 볼 수 있습니다. <slot> 태그 아래에 <span class="missing">이메일 미입력</span> 이와 같이 기본값에 해당하는 HTML 요소를 입력하면 됩니다. 


# 3. 조건에 따른 표현

slot으로 만들어진 컴포넌트를 불러와 사용할 경우 기본값을 이용해 입력하지 않은 내용에 대한 처리를 할 수 있었습니다. 이번에는 특정 slot을 사용했는지 아닌지를 체크할 수 있는 기능에 대해서 알아보겠습니다. '$$slots.slot이름' 을 통해서 slot정보를 받아올 수 있습니다. 그래서 이 정보의 유무를 체크하고 이를 통해서 특정영역을 보이고 안 보이게 하거나, 어떤 스타일(css)요소를 첨부하거나 하는 행동을 할 수 있습니다. 

예제를 조금 수정해서 실행시켜 보겠습니다.

`card.svelte`
```jsx
<style>
.contact-card {
		width: 300px;
		border: 1px solid #aaa;
		border-radius: 2px;
		box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
		padding: 1em;
	}

	h2 {
		padding: 0 0 0.2em 0;
		margin: 0 0 1em 0;
		border-bottom: 1px solid #ff3e00
	}

	.address, .email {
		padding: 0 0 0 1.5em;
		background:  0 50% no-repeat;
		background-size: 1em 1em;
		margin: 0 0 0.5em 0;
		line-height: 1.2;
	}

	.address { background-image: url(tutorial/icons/map-marker.svg) }
	.email   { background-image: url(tutorial/icons/email.svg) }
	.missing { color: #999 }
</style>

<article class="contact-card">
	<h2>
		<slot name="name">
			<span class="missing">이름 미입력</span>
		</slot>
	</h2>

	<div class="address">
		<slot name="address">
			<span class="missing">주소 미입력</span>
		</slot>
	</div>
	{#if $$slots.email}
		<div class="email">
			<hr />
			<slot name="email"></slot>
		</div>
	{/if}
</article>
```

App.svelte 도 조금 수정해보겠습니다. 

```jsx
<script>
	import Card from './card.svelte';
</script>

<Card>
	<span slot="name">
		홍길동
	</span>

	<span slot="address">
		서울특별시<br>
		여의도동
	</span>
</Card>

<br/>

<Card>
	<span slot="name">
		홍길동
	</span>

	<span slot="address">
		서울특별시<br>
		여의도동
	</span>
	<span slot="email">
		myemail@google.com
	</span>
</Card>
```

실행해 보시면 slot으로  email을 사용한 경우와 경우와 사용하지 않은 경우 나타나는 형태가 다름을 알 수 있습니다. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s6xxwyq0zncejyemwxl1.jpg)

지금까지의 기능만으로 정적인 마크업에 대해서는 어느정도 대응이 가능합니다. 

이제는 좀 더 동적인 기능 들을 slot을 이용해서 구현해 보도록 하겠습니다. slot으로 만들어진 card컴포넌트에 마우스오버 이벤트가 발생하면 색상이 변하고 폰트의 두께도 변경하는 기능을 추가 해보겠습니다.

이를위해 card컴포넌트에 .hovering {} 스타일을 추가하고, 스크립트영역에 hovering 상태값, enter, leave메소드를 만든 다음 <article class="contact-card" class:hovering on:mouseenter={enter} on:mouseleave={leave}> 이와 같이 이벤트에 연결하도록 하겠습니다. 

실행을 해보면 마우스를 올리면 다음과 같이 border가 나타나는 것을 확인할 수 있습니다.
 
`card.svelte`
```jsx
<style>
	.contact-card {
		width: 300px;
		border: 1px solid #aaa;
		border-radius: 2px;
		box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
		padding: 1em;
	}

	h2 {
		padding: 0 0 0.2em 0;
		margin: 0 0 1em 0;
		border-bottom: 1px solid #ff3e00
	}

	.address, .email {
		padding: 0 0 0 1.5em;
		background:  0 50% no-repeat;
		background-size: 1em 1em;
		margin: 0 0 0.5em 0;
		line-height: 1.2;
	}

	.address { background-image: url(tutorial/icons/map-marker.svg) }
	.email   { background-image: url(tutorial/icons/email.svg) }
	.missing { color: #999 }
	
	.hovering { background-color: #ffed99;}
</style>

<script>
	let hovering = false;
	const enter = () => hovering = true;
	const leave = () => hovering = false;
</script>


<article class="contact-card" class:hovering on:mouseenter={enter} on:mouseleave={leave}>
	<h2>
		<slot name="name">
			<span class="missing">이름 미입력</span>
		</slot>
	</h2>

	<div class="address">
		<slot name="address">
			<span class="missing">주소 미입력</span>
		</slot>
	</div>
	<div class="email">
		<slot name="email"></slot>
	</div>
</article>
```


# 4. slot에서 만들어진 상태값 전달받기

이번에는 현재 slot에서 만들어진 상태값인 hovering을 전달받아 사용하는 방법을 알아보겠습니다. 

이를 위해 우선 card컴포넌트에서 email slot에서 마치 props로 값을 전달할 때처럼 { hovering } 을 전달합니다. 

그리고 해당 컴포넌트를 사용하는 측에서는 let:hovering 으로 값을 받을 수 있습니다.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tg5xo0pa9mqtp3xeinth.jpg)

`card.svelte`
```jsx
<style>
	.contact-card {
		width: 300px;
		border: 1px solid #aaa;
		border-radius: 2px;
		box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
		padding: 1em;
	}

	h2 {
		padding: 0 0 0.2em 0;
		margin: 0 0 1em 0;
		border-bottom: 1px solid #ff3e00
	}

	.address, .email {
		padding: 0 0 0 1.5em;
		background:  0 50% no-repeat;
		background-size: 1em 1em;
		margin: 0 0 0.5em 0;
		line-height: 1.2;
	}

	.address { background-image: url(tutorial/icons/map-marker.svg) }
	.email   { background-image: url(tutorial/icons/email.svg) }
	.missing { color: #999 }
	
	.hovering { background-color: #ffed99;}
</style>

<script>
	let hovering = false;
	const enter = () => hovering = true;
	const leave = () => hovering = false;
</script>


<article class="contact-card" class:hovering on:mouseenter={enter} on:mouseleave={leave}>
	<h2>
		<slot name="name">
			<span class="missing">이름 미입력</span>
		</slot>
	</h2>

	<div class="address">
		<slot name="address">
			<span class="missing">주소 미입력</span>
		</slot>
	</div>
	<div class="email">
		<slot {hovering} name="email"></slot>
	</div>
</article>
```

이제 이렇게 전달받은 상태값에 따라 내용이 다르게 나타나게 할 수 있습니다 예제에서는 hovering이 true일 대 메일 주소에 b 태그가 나타나도록 했습니다.

`App.svelte`
```jsx
<script>
	import Card from './card.svelte';
</script>

<Card let:hovering>
	<span slot="name" >
		홍길동
	</span>

	<span slot="address">
		서울특별시 여의도동
	</span>
	<span slot="email">
		{#if hovering}
			<b>myemail@google.com</b> 
		{:else}
			myemail@google.com
		{/if}
	</span>
</Card>
```

실행시켜 보시면 마우스오버시 card컴포넌트 에서는 배경색이 변경되고, App.svelte의 email slot에서 적용한 <b> 태그가 적용되는 것을 볼 수 있을 것입니다. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oyfs19hnzrxno085qeie.png)
 

# 5. svelte:fragment

마지막으로 알아볼 slot의 기능을 fragment입니다. 

다음 코드를 보면 widget이라는 컴포넌트를 불러와 사용하고 있습니다. 여기에서 footer를 보면 이 slot footer를 사용하기 위해서 임의의 div를 만들 수 밖에 없는 상황인 것을 확인할 수 있습니다. 즉 의미없는 div가 추가되는 것으로 이때문에 디자인 요소등이 틀어질 가능성도 있습니다.

```jsx
<!-- Widget.svelte -->
<div>
	<slot name="header">No header was provided</slot>
	<p>Some content between header and footer</p>
	<slot name="footer"></slot>
</div>

<!-- App.svelte -->
<Widget>
	<h1 slot="header">Hello</h1>
	<div slot="footer">
		<p>All rights reserved.</p>
		<p>Copyright (c) 2019 Svelte Industries</p>
	</div>
</Widget>
```

이런경우에 특정 dom으로 감싸지 않고 바로 명명된 slot을 사용할 수 있는 방법이 바로 fragment입니다. 이용방법은 간단합니다. 다음과 같이 div로 되어 있는 곳을 svlete:fragment로 감싸주기만 하면 됩니다. 이를 이용하면 필요없는 돔 요소를 만들지 않고도 slot을 사용할 수 있다는 것을 기억하시기 바랍니다. 

```jsx
<!-- Widget.svelte -->
<div>
	<slot name="header">No header was provided</slot>
	<p>Some content between header and footer</p>
	<slot name="footer"></slot>
</div>

<!-- App.svelte -->
<Widget>
	<h1 slot="header">Hello</h1>
	<svelte:fragment slot="footer">
		<p>All rights reserved.</p>
		<p>Copyright (c) 2019 Svelte Industries</p>
	</svelte:fragment>
</Widget>
```

현재 프론트엔드 프레임워크들은 컴포넌트들을 좀 더 효율적으로 개발할 수 있는 방향으로 발전하고 있습니다. 이런 관점에서 Svelte의 slot은 마크업영역을 효율적으로 재사용할 수 있게 도와주는 훌륭한 API입니다. slot과 같은 장치들이 처음에는 조금 어색할 수 있습니다. 하지만 이런 장치들을 자신이 만들려는 웹앱의 어떤 부분들에 사용하면 좋을지에 대한 고민을 하고 적용해 보시면, 좀 더 효율적인 개발이 가능할 것입니다. 

그럼 다음에 또 svelte에 대한 다른 주제로 찾아오겠습니다. 감사합니다.