6 분 소요

Component composition

Slots

  • 컴포넌트에도 자식 요소를 포함시키기 위해 사용하는 것으로 자식요소의 위치를 지정해준다.

예시

  • App.svelte에서 Box.svelte라는 컴포넌트를 불러와서 사용한다. 이때 App.svelte에서 자식요소를 Box.svelte로 넘겨서 꽂을 수 있도록 위치를 지정한 것이 slot이다.
App.svelte
1
2
3
4
5
6
7
8
<script>
  import Box from './Box.svelte';
</script>

<Box>
  <h2>Hello!</h2>
  <p>This is a box. It can contain anything.</p>
</Box>
Box.svelte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="box">
  <slot></slot>
</div>

<style>
  .box {
    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>

Slot fallbacks(대안)

  • Slot에 아무것도 전달되지 않았을때 대안을 구현해 놓을 수 있다.

예시

  • App.svelte에서 Box component를 2번 사용하였는데, 1개는 자식요소를 넣어주었고, 1개는 넣어주지 않았다.
  • 자식요소를 넣어준 component는 자식요소가 셋팅되었고, 자식요소를 넣어주지 않은 component는 fallback으로 구현해놓은 부분이 셋팅되었다.
App.svelte
1
2
3
4
5
6
7
8
9
10
<script>
  import Box from './Box.svelte';
</script>

<Box>
  <h2>Hello!</h2>
  <p>This is a box. It can contain anything.</p>
</Box>

<Box/>
Box.svelte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="box">
  <slot>
    <em>no content was provided</em>
  </slot>
</div>

<style>
  .box {
    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>

Named slots

  • slot을 여러개 사용하는 경우에 slot에 이름을 명명하여 해당 이름의 slot에 자식요소를 전달시킬 수 있다.
  • 개쩔고 유용한 기능이다.

예시

  • App.svelte에서 ContactCard.svelte 파일의 slot에 자식요소를 전달하되, 이름 기준으로 전달해준다.
  • 따라서 App.svelte의 span 순서가 바뀌어도 어차피 ContactCard에 이름에 맞게 셋팅되기 때문에 신기하다.
App.svelte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
	import ContactCard from './ContactCard.svelte';
</script>

<ContactCard>
	<span slot="name">
		P. Sherman
	</span>

	<span slot="address">
		42 Wallaby Way<br>
		Sydney
	</span>
</ContactCard>
ContactCard.svelte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<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>

<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 0 no-repeat;
    background-size: 20px 20px;
    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>

Checking for slot content

  • slot에 컨텐츠를 전달받았는지에 따라 wapper(div 등 감싸는 것)의 class를 적용/미적용, 혹은 {#if}{/if}문 등의 표현식을 사용하고 싶을때 {$$slots.전달 컨텐츠의 name}을 사용함으로써 알수 있다.
  • $$slots 은 부모 component로부터 전달된 slots의 이름을 key로 갖는 객체이다. 전달된게 없으면 항목이 없다.

예시

  • App.svelte에서 Project.svelte component 2번, Commnet.svelte component를 1번 사용한다.
  • Project component 안에 slot name을 comments로 명명한 자식 컨텐츠를 넣고, 그 안에 Comment component를 다시 넣었다.
  • 이때 첫번째 Project component에는 comments라는 자식요소를 넣었지만, 두번쩨 Project component에는 slot에 전달될 contents를 넣지 않았다.
  • 따라서 Proejct.svelte에 기술된 $$slots.comments의 값 여부에 따라 class 적용 및 if문에 따라 element를 노출한다.
App.svelte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<script>
  import Project from './Project.svelte'
  import Comment from './Comment.svelte'
</script>

<h1>
  Projects
</h1>

<ul>
  <li>
    <Project
      title="Add TypeScript support"
      tasksCompleted={25}
      totalTasks={57}
    >
      <div slot="comments">
        <Comment name="Ecma Script" postedAt={new Date('2020-08-17T14:12:23')}>
        <p>Those interface tests are now passing.</p>
        </Comment>
      </div>
    </Project>
  </li>
  <li>
    <Project
      title="Update documentation"
      tasksCompleted={18}
      totalTasks={21}
    />
  </li>
</ul>

<style>
  h1 {
    font-weight: 300;
    margin: 0 1rem;
  }

  ul {
    list-style: none;
    padding: 0;
    margin: 0.5rem;
    display: flex;
  }

  @media (max-width: 600px) {
    ul {
      flex-direction: column;
    }
  }

  li {
    padding: 0.5rem;
    flex: 1 1 50%;
    min-width: 200px;
  }
</style>
Comment.svelte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<script>
  export let name;
  export let postedAt;

  $: avatar = `https://ui-avatars.com/api/?name=${name.replace(/ /g, '+')}&rounded=true&background=ff3e00&color=fff&bold=true`;
</script>

<article>
  <div class="header">
    <img src={avatar} alt="" height="32" width="32">
    <div class="details">
      <h4>{name}</h4>
      <time datetime={postedAt.toISOString()}>{postedAt.toLocaleDateString()}</time>
    </div>
  </div>
  <div class="body">
    <slot></slot>
  </div>
</article>

<style>
  article {
    background-color: #fff;
    border: 1px #ccc solid;
    border-radius: 4px;
    padding: 1rem;
  }

  .header {
    align-items: center;
    display: flex;
  }

  .details {
    flex: 1 1 auto;
    margin-left: 0.5rem
  }

  h4 {
    margin: 0;
  }

  time {
    color: #777;
    font-size: 0.75rem;
    text-decoration: underline;
  }

  .body {
    margin-top: 0.5rem;
  }

  .body :global(p) {
    margin: 0;
  }
</style>
Project.svelte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<script>
  export let title;
  export let tasksCompleted = 0;
  export let totalTasks = 0;
</script>

<article class:has-discussion={$$slots.comments}>
  <div>
    <h2>{title}</h2>
    <p>{tasksCompleted}/{totalTasks} tasks completed</p>
  </div>
  {#if $$slots.comments}
  <div class="discussion">
    <h3>Comments</h3>
    <slot name="comments"></slot>
  </div>
  {/if}
</article>

<style>
  article {
    border: 1px #ccc solid;
    border-radius: 4px;
    position: relative;
  }

  article > div {
    padding: 1.25rem;
  }

  article.has-discussion::after {
    content: '';
    background-color: #ff3e00;
    border-radius: 10px;
    box-shadow: 0 2px 4px rgba(0,0,0,0.2);
    height: 20px;
    position: absolute;
    right: -10px;
    top: -10px;
    width: 20px;
  }

  h2,
  h3 {
    margin: 0 0 0.5rem;
  }

  h3 {
    font-size: 0.875rem;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }

  p {
    color: #777;
    margin: 0;
  }

  .discussion {
    background-color: #eee;
    border-top: 1px #ccc solid;
  }
</style>

Slot props

  • let이라는 지시어를 사용하여 부모 component ↔ 자식 component 사이에 값을 전달하며 공유할 수 있다.

예시

  • Hoverable.svelte에서 이벤트 handler에 따라 hovering이라는 변수의 값이 바뀐다.
  • 해당 값은 부모 component인 App.svelte에서 let:hovering={active}라고 정의된 active에 값이 전달 및 셋팅된다.
  • App.svelte는 active값에 따라 class를 적용하거나 elements를 노출한다.
  • Named slots에서도 props 할 수 있다.
App.svelte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<script>
  import Hoverable from './Hoverable.svelte';
</script>

<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 let:hovering={active}>
  <div class:active>
    {#if active}
    <p>I am being hovered upon.</p>
    {:else}
    <p>Hover over me!</p>
    {/if}
  </div>
</Hoverable>

<style>
  div {
    padding: 1em;
    margin: 0 0 1em 0;
    background-color: #eee;
  }

  .active {
    background-color: #ff3e00;
    color: white;
  }
</style>
Hoverable.svelte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
  let hovering;

  function enter() {
    hovering = true;
  }

  function leave() {
    hovering = false;
  }
</script>

<div on:mouseenter={enter} on:mouseleave={leave}>
  <slot hovering={hovering}></slot>
</div>

참고 사이트

# Svelte 공식사이트 튜토리얼 - Bindings

댓글남기기