슬롯 — 컴포넌트 합성의 핵심 패턴
React의
childrenprop처럼, Svelte에서도 컴포넌트 안에 컨텐츠를 끼워 넣을 수 있습니다 — Svelte 5에서는 Snippet이 그 역할을 합니다.
개념 정의
슬롯(Slot) 은 부모 컴포넌트가 자식 컴포넌트의 마크업 일부를 주입할 수 있게 하는 패턴입니다. Svelte 5에서는 전통적인 <slot> 대신 Snippet 과 children prop을 권장합니다.
children — 기본 컨텐츠 주입
<!-- Card.svelte -->
<script>
let { children, title } = $props();
</script>
<div class="card">
<h2>{title}</h2>
<div class="card-body">
{@render children()}
</div>
</div>
<style>
.card { border: 1px solid #ddd; border-radius: 8px; padding: 1rem; }
.card-body { margin-top: 0.5rem; }
</style>
<!-- App.svelte -->
<script>
import Card from './Card.svelte';
</script>
<Card title="공지사항">
<p>이 내용이 card-body 안에 렌더링됩니다.</p>
<button>확인</button>
</Card>
{@render children()}은 부모가 전달한 컨텐츠를 렌더링합니다.
기본 컨텐츠 (Fallback)
<!-- Alert.svelte -->
<script>
let { children, type = 'info' } = $props();
</script>
<div class="alert alert-{type}">
{#if children}
{@render children()}
{:else}
<p>기본 알림 메시지입니다.</p>
{/if}
</div>
<!-- children을 전달하지 않으면 기본 컨텐츠 표시 -->
<Alert type="warning" />
<!-- children을 전달하면 커스텀 컨텐츠 표시 -->
<Alert type="error">
<strong>오류 발생!</strong> 잠시 후 다시 시도해주세요.
</Alert>
Named Snippet — 여러 영역 주입
<!-- Layout.svelte -->
<script>
let { header, children, footer } = $props();
</script>
<div class="layout">
<header>
{#if header}
{@render header()}
{/if}
</header>
<main>
{@render children()}
</main>
<footer>
{#if footer}
{@render footer()}
{:else}
<p>기본 푸터</p>
{/if}
</footer>
</div>
<!-- App.svelte -->
<script>
import Layout from './Layout.svelte';
</script>
<Layout>
{#snippet header()}
<nav>내비게이션 바</nav>
{/snippet}
<p>메인 컨텐츠가 여기에 들어갑니다.</p>
{#snippet footer()}
<p>커스텀 푸터 2026</p>
{/snippet}
</Layout>
Snippet에 데이터 전달
<!-- List.svelte -->
<script>
let { items, renderItem, children } = $props();
</script>
<ul>
{#each items as item, index}
<li>
{#if renderItem}
{@render renderItem(item, index)}
{:else}
{item}
{/if}
</li>
{/each}
</ul>
<!-- App.svelte -->
<script>
import List from './List.svelte';
const users = [
{ name: '홍길동', role: 'admin' },
{ name: '김철수', role: 'user' },
];
</script>
<List items={users}>
{#snippet renderItem(user, index)}
<strong>{index + 1}. {user.name}</strong>
<span class="badge">{user.role}</span>
{/snippet}
</List>
이 패턴은 React의 Render Props 패턴과 동일한 역할을 합니다.
실전 예시 — Modal 컴포넌트
<!-- Modal.svelte -->
<script>
let { open = $bindable(false), title, children, actions } = $props();
function close() {
open = false;
}
</script>
{#if open}
<div class="backdrop" onclick={close}>
<div class="modal" onclick={(e) => e.stopPropagation()}>
<div class="modal-header">
<h2>{title}</h2>
<button onclick={close}>X</button>
</div>
<div class="modal-body">
{@render children()}
</div>
{#if actions}
<div class="modal-actions">
{@render actions()}
</div>
{/if}
</div>
</div>
{/if}
<style>
.backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: grid; place-items: center; }
.modal { background: white; border-radius: 8px; padding: 1.5rem; min-width: 400px; }
</style>
<!-- App.svelte -->
<script>
import Modal from './Modal.svelte';
let showModal = $state(false);
</script>
<button onclick={() => showModal = true}>모달 열기</button>
<Modal bind:open={showModal} title="확인">
<p>정말 삭제하시겠습니까?</p>
{#snippet actions()}
<button onclick={() => showModal = false}>취소</button>
<button onclick={() => { /* 삭제 로직 */ showModal = false; }}>삭제</button>
{/snippet}
</Modal>
면접 포인트
- "Svelte 5에서 slot이 Snippet으로 바뀐 이유는?":
<slot>은 Web Components의 slot과 혼동될 수 있고, 데이터 전달 방식이let:디렉티브로 제한적이었습니다. Snippet은 일반 함수처럼 매개변수를 받을 수 있어 더 유연합니다. - "React children과 Svelte children의 차이는?": React의 children은 ReactNode 타입의 prop이고, Svelte의 children은
{@render}로 호출하는 Snippet입니다. Svelte 방식이 조건부 렌더링과 데이터 전달에서 더 명시적입니다.
정리
Snippet은 Svelte 5에서 컴포넌트 합성의 핵심 패턴입니다. children으로 기본 컨텐츠를, named snippet으로 여러 영역을, 매개변수로 데이터 전달까지 — 이 세 가지만 알면 재사용 가능한 컴포넌트를 자유자재로 만들 수 있습니다.
댓글 로딩 중...