슬롯(Slots)

카탈로그
  1. 1. 슬롯에 들어가는 내용(Slot Content)
  2. 2. 컴파일될 때의 범위(Compilation Scope)
  3. 3. 기본값 지정(Fallback Content)
  4. 4. 이름이 있는 슬롯(Named Slots)
  5. 5. 범위가 있는 슬롯(Scoped Slots)
    1. 5.1. 단독 디폴트 슬롯을 위한 축약 문법(Abbreviated Syntax for Lone Default Slots)
    2. 5.2. 슬롯 속성 구조분해(Destructuring Slot Props)
  6. 6. 가변 슬롯 이름(Dynamic Slot Names)
  7. 7. 이름이 있는 슬롯 디렉티브의 단축표기(Named Slots Shorthand)
  8. 8. 다른 사례들(Other Examples)
  9. 9. 삭제될 문법
    1. 9.1. slot 인수를 사용하는 이름이 있는 슬롯
    2. 9.2. slot-scope 인수를 사용하는 범위가 있는 슬롯

이 페이지는 여러분이 이미 컴포넌트 기초를 읽었다고 가정하고 쓴 내용입니다. 컴포넌트가 처음이라면 기초 문서를 먼저 읽으시기 바랍니다.

2.6.0 버전에서 ‘이름이 있는 슬롯’과 ‘범위를 가지는 슬롯’을 위한 새로운 통합 문법(‘v-slot’ 디렉티브)을 도입했습니다. 새 디렉티브가 slotslot-scope 속성을 대체하고 두 속성은 이제 사라질 예정입니다. 아직 삭제된 건 아니고 문서는 여기에서 볼 수 있습니다. 새 문법이 도입된 이유는 이 RFC에서 찾아볼 수 있습니다.(역자 주: 간략히 설명드리자면 기존 문법이 혼란스러운 부분이 있기 때문입니다. ‘이름이 있는 슬롯’과 ‘범위를 가지는 슬롯’을 동시에 쓸 때 좀 더 간결해지긴 하는데 슬롯 자체가 이해가 쉽지는 않습니다.)

슬롯에 들어가는 내용(Slot Content)

Vue에 있는 컨텐트 배포 API는 <slot> 요소를 컨텐트 배포 통로로 사용하는 Web Components spec draft에서 영향을 받았습니다.(역자 주: 웹컴포넌트는 2011년에 최초로 제안된 개념으로 구글에서 주로 밀고 있습니다. 표준안만 나오면 가장 가볍고 보편적인 웹 프레임워크가 되었겠지만 브라우저 제작사들이 아직 표준안을 도출하지 못하고 있습니다. 대신 논의 과정에서 나온 여러 개념들이 리액트, 앵귤러, 뷰 등의 웹 프레임워크에 도입되어서 사용되고 있습니다.)

그래서 아래와 같은 컴포넌트를 만들 수 있습니다.

1
2
3
<navigation-link url="/profile">
Your Profile
</navigation-link>

그리고 <navigation-link> 템플릿을 아래와 같이 만들 수 있습니다.

1
2
3
4
5
6
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>

컴포넌트를 렌더링할 때 <slot></slot>이 “Your Profile”로 교체됩니다. 슬롯에는 HTML 같은 템플릿 코드를 포함시킬 수 있기 때문입니다.

1
2
3
4
5
<navigation-link url="/profile">
<!-- Font Awesome 아이콘을 추가합시다 -->
<span class="fa fa-user"></span>
Your Profile
</navigation-link>

다른 컴포넌트(역자 주: <font-awesome-icon>)도 가능합니다.

1
2
3
4
5
<navigation-link url="/profile">
<!-- 컴포넌트로 아이콘을 추가해봅시다 -->
<font-awesome-icon name="user"></font-awesome-icon>
Your Profile
</navigation-link>

만약 <navigation-link> 템플릿이 <slot> 요소를 가지고 있지 않다면 그 자리에 들어갔어야 할 모든 내용이 무시될 것입니다.

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

슬롯 안에 데이터 옵션을 사용하고 싶을 수 있습니다. 아래의 예를 봅시다.

1
2
3
<navigation-link url="/profile">
Logged in as {{ user.name }}
</navigation-link>

여기서 슬롯은 같은 템플릿의 나머지와 똑같은 인스턴스 속성(즉 같은 “범위”)에 연결되어 있습니다. 슬롯이 <navigation-link>의 범위에 연결된 것이 아닌 거죠. 예를 들어 url에 접근하려고 하면 작동하지 않을 것입니다.

1
2
3
4
5
6
7
8
<navigation-link url="/profile">
Clicking here will send you to: {{ url }}
<!--
`url`은 undefined로 나올 겁니다. 이 데이터는 <navigation-link>_로_
넘어가지만 <navigation-link> 컴포넌트 _안에_ 정의되어 있지는
않으니까요.
-->
</navigation-link>

이 규칙을 기억하세요.

부모 템플릿 안에 있는 것들은 부모 컴포넌트의 범위에 컴파일되고 자식 템플릿 안에 있는 것들은 자식 컴포넌트의 범위에 컴파일됩니다.

기본값 지정(Fallback Content)

아무 컨텐트도 전달되지 않았을 때 슬롯에 렌더링시킬 대비책(즉 기본값)을 지정해놓는 것이 유용한 경우가 있을 수 있습니다. <submit-button> 컴포넌트의 예를 살펴봅시다.

1
2
3
<button type="submit">
<slot></slot>
</button>

보통은 <button> 안에 “Submit” 텍스트가 들어가기를 원할 때가 많습니다. “Submit”을 기본값으로 만들기 위해서는 <slot> 태그 사이에 넣어야 합니다.

1
2
3
<button type="submit">
<slot>Submit</slot>
</button>

이제 부모 컴포넌트의 <submit-button>를 사용할 때 아래와 같이 슬롯에 해당하는 아무 내용을 전달하지 않으면

1
<submit-button></submit-button>

기본값인 “Submit”이 렌더링될 것입니다.

1
2
3
<button type="submit">
Submit
</button>

하지만 슬롯에 어떤 내용을 전달하면

1
2
3
<submit-button>
Save
</submit-button>

전달된 내용이 렌더링되겠죠.

1
2
3
<button type="submit">
Save
</button>

이름이 있는 슬롯(Named Slots)

2.6.0+에서 업데이트가 있었습니다. 여기에서 slot 속성을 사용하는 기존의 문법을 보실 수 있습니다.

여러 개의 슬롯을 쓰면 더 유용할 때가 있습니다. 예를 들어 <base-layout> 컴포넌트의 아래 템플릿을 봅시다.

1
2
3
4
5
6
7
8
9
10
11
<div class="container">
<header>
<!-- 헤더는 여기에 넣을 겁니다 -->
</header>
<main>
<!-- 본문은 여기에 넣을 겁니다 -->
</main>
<footer>
<!-- 푸터는 여기에 넣을 -->
</footer>
</div>

이런 경우를 위해서 <slot> 요소는 서로 다른 슬롯들을 정의할 때 쓸 수 있는 name이라는 특별한 속성을 가지고 있습니다.

1
2
3
4
5
6
7
8
9
10
11
<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’을 넣으면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>

<p>A paragraph for the main content.</p>
<p>And another one.</p>

<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>

이제 <template> 요소들의 모든 내용물이 적절한 슬롯으로 전달되었습니다. v-slot을 쓴 <template>으로 싸여있지 않은 내용물들은 디폴트 슬롯에 해당되는 것으로 간주합니다.

하지만 원한다면 명시적으로 <template>에 ‘default’를 표시하고 그 안에 내용을 넣을 수도 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>

<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>

<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>

양쪽 모두 렌더링된 HTML은 아래와 같게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>

삭제될 예정인 slot 속성과 다르게 v-slot<template> 태그에 추가할 수 있다는 점을 유의하시기 바랍니다.(예외가 하나 있기는 합니다)

범위가 있는 슬롯(Scoped Slots)

2.6.0+에서 업데이트가 있었습니다. 여기에서 slot-scope 속성을 사용하는 기존의 문법을 보실 수 있습니다.

어떨 때는 자식 컴포넌트에서만 접근할 수 있는 데이터에서 슬롯에 필요한 내용을 가져와야 할 수 있습니다. 아래 템플릿의 <current-user> 컴포넌트의 예를 살펴봅시다.

1
2
3
<span>
<slot>{{ user.lastName }}</slot>
</span>

아래와 같이 기본값을 사용자의 성말고 이름으로 바꾸고 싶을 수도 있습니다.

1
2
3
<current-user>
{{ user.firstName }}
</current-user>

하지만 이 파일은 작동하지 않습니다. 왜냐하면 <current-user> 컴포넌트만 user에 접근할 수 있는데 슬롯에 제공되는 내용들은 부모 컴포넌트에서 렌더링되기 때문입니다.

부모 컴포넌트의 슬롯에서 user를 쓸려면 user<slot> 요소에 속성으로 연결해야 합니다.

1
2
3
4
5
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>

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

1
2
3
4
5
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>

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

단독 디폴트 슬롯을 위한 축약 문법(Abbreviated Syntax for Lone Default Slots)

위의 사례와 같이 제공된 내용이 디폴트 슬롯 밖에 없으면 컴포넌트의 태그를 슬롯의 템플릿으로 바로 쓸 수 있습니다. 즉 v-slot을 컴포넌트에다 쓸 수 있다는 것입니다.(역자 주: 바로 위의 컴포넌트 코드와 비교하세요.)

1
2
3
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>

더 짧게도 만들 수 있습니다. 앞에서 다뤘듯이 특정되지 않은 내용은 디폴트 슬롯에 해당하는 것으로 간주하기 때문에, 속성이 없는 v-slot도 디폴트 슬롯으로 간주합니다.(역자 주: 위의 코드에서는 ‘default’가 ‘name’속성입니다.)

1
2
3
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
</current-user>

범위를 모호하게 만들기 때문에 디폴트 슬롯을 위한 축약 문법은 이름이 있는 슬롯들과 함께 쓸 수 없습니다.

1
2
3
4
5
6
7
<!-- 불가. 경고가 뜰 겁니다 -->
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
<template v-slot:other="otherSlotProps">
slotProps is NOT available here
</template>
</current-user>

여러 개의 슬롯이 있을 때는 <template>에 기초한 문법 전체를 모든 슬롯에 쓰기 바랍니다.

1
2
3
4
5
6
7
8
9
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>

<template v-slot:other="otherSlotProps">
...
</template>
</current-user>

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

프레임워크 내부에서 ‘범위가 있는 슬롯’은 하나의 인수(역자 주: 슬롯 속성)를 가지는 함수로 슬롯에 들어가는 내용을 감싸는 방식으로 작동합니다.

1
2
3
function (slotProps) {
// ... slot content ...
}

v-slot의 값은 함수 정의식의 인수 위치에서 가능한 어떤 종류의 자바스크립트 표현식도 다 가능합니다. 그러므로 (싱글 파일 컴포넌트모던 브라우저처럼) 지원되는 모든 환경에서 아래와 같이 특정 슬롯 속성을 추출하는 ES2015 구조분해를 할 수 있다는 뜻입니다.

1
2
3
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>

이를 통해서 템플릿을 더 깨끗하게 만들 수 있습니다. 특히 슬롯에서 다양한 슬롯 속성들을 쓸 경우에 그렇습니다. 또 다른 기능으로 userperson으로 바꾸는 것처럼 속성의 이름을 다시 정할 수도 있죠.

1
2
3
<current-user v-slot="{ user: person }">
{{ person.firstName }}
</current-user>

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

1
2
3
<current-user v-slot="{ user = { firstName: 'Guest' } }">
{{ user.firstName }}
</current-user>

가변 슬롯 이름(Dynamic Slot Names)

2.6.0+ 에서 추가

가변 디렉티브 인수는 가변 슬롯 이름을 정의하는 방식으로 v-slot에서도 작동합니다.

1
2
3
4
5
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>

이름이 있는 슬롯 디렉티브의 단축표기(Named Slots Shorthand)

2.6.0+ 에서 추가

v-onv-bind처럼 v-slot도 단축표기를 가지고 있습니다. 인수 앞에 쓰는 부분(v-slot:)을 특수 기호인 #으로 대체하는 것입니다. 예를 들어 v-slot:header#header로 쓸 수도 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
<base-layout>
<template #header>
<h1>Here might be a page title</h1>
</template>

<p>A paragraph for the main content.</p>
<p>And another one.</p>

<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>

하지만 다른 디렉티브와 마찬가지로 단축 표기는 오직 인수가 있을 때만 가능합니다. 즉 아래와 같은 문법은 불가능합니다.

1
2
3
4
<!-- 이런 문법을 쓰면 경고가 나올 겁니다 -->
<current-user #="{ user }">
{{ user.firstName }}
</current-user>

그리고 단축 표기를 쓰려면 반드시 슬롯의 이름을 특정해야 합니다.

1
2
3
<current-user #default="{ user }">
{{ user.firstName }}
</current-user>

다른 사례들(Other Examples)

슬롯 속성을 통해 슬롯을 입력되는 속성에 따라 다른 내용을 렌더링할 수 있는 재사용가능한 템플릿으로 변환할 수 있습니다. 부모 컴포넌트를 레이아웃 용도로만 사용하고 데이터 로직을 캡슐화한 재사용가능 컴포넌트를 디자인할 때 가장 유용한 방법입니다.

아래에서 목록의 레이아웃과 필터링 로직을 담은 <todo-list> 컴포넌트를 쓸 것입니다.

1
2
3
4
5
6
7
8
<ul>
<li
v-for="todo in filteredTodos"
v-bind:key="todo.id"
>
{{ todo.text }}
</li>
</ul>

각각의 할 일에 대해서 하드코딩된 내용을 쓰는 대신 모든 할일 들을 슬롯으로 만들어서 부모 컴포넌트가 관리할 수 있게 만들 수 있습니다. 그리고 todo를 슬롯 속성으로 연결시키는 거죠.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<ul>
<li
v-for="todo in filteredTodos"
v-bind:key="todo.id"
>
<!--
각 할일에 대해 슬롯을 만들고
`todo` 객체를 슬롯 속성으로 보냅니다.
-->
<slot name="todo" v-bind:todo="todo">
<!-- 기본값 -->
{{ todo.text }}
</slot>
</li>
</ul>

이제 <todo-list> 컴포넌트를 쓸 때 할일 아이템들을 위해서 자식 컴포넌트의 데이터에도 접근할 수 있는 대체 <template>을 따로 정의할 수도 있습니다.

1
2
3
4
5
6
<todo-list v-bind:todos="todos">
<template v-slot:todo="{ todo }">
<span v-if="todo.isComplete"></span>
{{ todo.text }}
</template>
</todo-list>

하지만 이 정도는 범위가 있는 슬롯으로 무엇을 할 수 있는지 아주 조금 살펴본 것에 불과합니다. 실제로 범위가 있는 슬롯의 사용 예시를 보시려면 Vue Virtual Scroller, Vue Promised, Portal Vue같은 라이브러리들을 둘러볼 것을 추천드립니다.

삭제될 문법

v-slot 디렉티브는 slotslot-scope 인수들을 대체하는, 더 발전된 API로 Vue 2.6.0에 도입되었습니다. 새 문법이 도입된 이유는 이 RFC에서 찾아볼 수 있습니다. slotslot-scope 인수는 앞으로도 2.x 버전에서는 계속 지원될 것입니다. 하지만 Vue 3에서는 공식적으로 삭제될 예정입니다.

slot 인수를 사용하는 이름이 있는 슬롯

2.6.0+ 부터 지원 중단 예정. 여기에서 권장되는 새 문법을 확인하세요.

내용물을 부모 컴포넌트에서 이름이 있는 슬롯에 보내려면 slot 인수를 <template>에서 사용해야 합니다.(여기에서 예시로 사용했던 <base-layout> 컴포넌트를 사용하겠습니다.)

1
2
3
4
5
6
7
8
9
10
11
12
<base-layout>
<template slot="header">
<h1>Here might be a page title</h1>
</template>

<p>A paragraph for the main content.</p>
<p>And another one.</p>

<template slot="footer">
<p>Here's some contact info</p>
</template>
</base-layout>

혹은 slot 인수를 일반 요소(element)에 바로 사용할 수도 있습니다.

1
2
3
4
5
6
7
8
<base-layout>
<h1 slot="header">Here might be a page title</h1>

<p>A paragraph for the main content.</p>
<p>And another one.</p>

<p slot="footer">Here's some contact info</p>
</base-layout>

여전히 이름이 없는 슬롯이 하나 있는데 이를 디폴트 슬롯이라고 하고 해당하는 슬롯이 없는 모든 내용들이 여기에 들어갑니다. 위의 두 예시 모두 HTML로 렌더링하면 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>

slot-scope 인수를 사용하는 범위가 있는 슬롯

2.6.0+ 부터 지원 중단 예정. 여기에서 권장되는 새 문법을 확인하세요.

슬롯으로 전달된 속성들을 받기 위해서 부모 컴포넌트는 slot-scope 인수와 함께 <template>을 사용할 수 있습니다.(여기에서 예시로 사용했던 <slot-example> 컴포넌트를 사용하겠습니다.)

1
2
3
4
5
<slot-example>
<template slot="default" slot-scope="slotProps">
{{ slotProps.msg }}
</template>
</slot-example>

여기서 slot-scope은 넘겨받은 속성들을 slotProps 변수로 선언하고 <template> 범위 안에서 사용할 수 있도록 만들었습니다. slotProps라는 이름은 자바스크립트에서 함수의 인수 이름을 정하듯이 자유롭게 정할 수 있습니다.

slot="default"는 생략할 수도 있습니다.

1
2
3
4
5
<slot-example>
<template slot-scope="slotProps">
{{ slotProps.msg }}
</template>
</slot-example>

slot-scope 인수는 컴포넌트와 같이 <template> 요소가 아닌 곳에 바로 사용될 수도 있습니다.

1
2
3
4
5
<slot-example>
<span slot-scope="slotProps">
{{ slotProps.msg }}
</span>
</slot-example>

v-slot의 값은 함수 정의식의 인수 위치에서 가능한 어떤 종류의 자바스크립트 표현식도 다 가능합니다. 그러므로 (싱글 파일 컴포넌트모던 브라우저처럼) 지원되는 모든 환경에서 아래와 같이 특정 슬롯 속성을 추출하는 ES2015 구조분해를 할 수 있다는 뜻입니다.

1
2
3
4
5
<slot-example>
<span slot-scope="{ msg }">
{{ msg }}
</span>
</slot-example>

아래는 여기에서 사례로 설명한 <todo-list>와 똑같은 사례입니다.

1
2
3
4
5
6
<todo-list v-bind:todos="todos">
<template slot="todo" slot-scope="{ todo }">
<span v-if="todo.isComplete"></span>
{{ todo.text }}
</template>
</todo-list>