Snippets — 재사용 가능한 마크업 조각
Snippet은 "컴포넌트를 만들기엔 과한데, 마크업을 반복하기엔 아까운" 그 중간 지대를 채웁니다.
개념 정의
Snippet 은 Svelte 5에서 도입된 재사용 가능한 마크업 블록입니다. {#snippet name()}...{/snippet}으로 선언하고, {@render name()}으로 렌더링합니다. 별도 컴포넌트 파일을 만들지 않고도 마크업을 재사용할 수 있습니다.
기본 사용법
<script>
let users = $state([
{ name: '홍길동', role: 'admin', active: true },
{ name: '김철수', role: 'user', active: false },
{ name: '이영희', role: 'editor', active: true },
]);
</script>
<!-- Snippet 선언 -->
{#snippet badge(text, variant)}
<span class="badge badge-{variant}">{text}</span>
{/snippet}
<!-- Snippet 사용 -->
{#each users as user}
<div class="user-card">
<h3>{user.name}</h3>
{@render badge(user.role, user.role === 'admin' ? 'red' : 'blue')}
{@render badge(user.active ? '활성' : '비활성', user.active ? 'green' : 'gray')}
</div>
{/each}
<style>
.badge { padding: 2px 8px; border-radius: 4px; font-size: 0.8rem; color: white; }
.badge-red { background: #e74c3c; }
.badge-blue { background: #3498db; }
.badge-green { background: #27ae60; }
.badge-gray { background: #95a5a6; }
</style>
컴포넌트 Props로 Snippet 전달
Snippet은 props로 전달하여 컴포넌트의 렌더링을 커스터마이징할 수 있습니다.
<!-- Table.svelte -->
<script>
let { data, columns, row, header } = $props();
</script>
<table>
<thead>
<tr>
{#if header}
{@render header()}
{:else}
{#each columns as col}
<th>{col.label}</th>
{/each}
{/if}
</tr>
</thead>
<tbody>
{#each data as item}
<tr>
{#if row}
{@render row(item)}
{:else}
{#each columns as col}
<td>{item[col.key]}</td>
{/each}
{/if}
</tr>
{/each}
</tbody>
</table>
<!-- App.svelte -->
<script>
import Table from './Table.svelte';
const users = [
{ id: 1, name: '홍길동', email: 'hong@test.com', role: 'admin' },
{ id: 2, name: '김철수', email: 'kim@test.com', role: 'user' },
];
const columns = [
{ key: 'name', label: '이름' },
{ key: 'email', label: '이메일' },
{ key: 'role', label: '역할' },
];
</script>
<Table {data} {columns}>
{#snippet row(user)}
<td><strong>{user.name}</strong></td>
<td><a href="mailto:{user.email}">{user.email}</a></td>
<td>{user.role === 'admin' ? '관리자' : '사용자'}</td>
{/snippet}
</Table>
Snippet vs 컴포넌트
| 특성 | Snippet | 컴포넌트 |
|---|---|---|
| 별도 파일 | 불필요 | 필요 |
| 자체 상태 | 부모 스코프 공유 | 독립적 |
| 재사용 범위 | 같은 파일 또는 props | 어디서든 import |
| 스타일 스코프 | 부모 컴포넌트 | 독립적 |
| 용도 | 반복 마크업 조각 | 독립적 UI 단위 |
재귀 Snippet
<script>
let tree = $state({
name: 'root',
children: [
{ name: 'src', children: [
{ name: 'routes', children: [
{ name: '+page.svelte', children: [] }
]},
{ name: 'lib', children: [] }
]},
{ name: 'static', children: [] }
]
});
</script>
{#snippet treeNode(node, depth)}
<div style:padding-left="{depth * 20}px">
{node.children.length > 0 ? '📁' : '📄'} {node.name}
</div>
{#each node.children as child}
{@render treeNode(child, depth + 1)}
{/each}
{/snippet}
{@render treeNode(tree, 0)}
면접 포인트
- "Snippet과 slot의 차이는?": Snippet은 Svelte 5에서 slot을 대체합니다. Snippet은 일반 함수처럼 매개변수를 받을 수 있어 더 유연하고, TypeScript 타입 지원도 우수합니다. slot의
let:디렉티브보다 직관적입니다. - "Snippet을 언제 쓰고 컴포넌트를 언제 쓰나요?": 자체 상태나 독립적인 스타일이 필요하면 컴포넌트, 같은 파일 내에서 마크업 반복을 줄이거나 컴포넌트의 렌더링을 커스터마이징할 때는 Snippet이 적합합니다.
정리
Snippet은 컴포넌트와 인라인 마크업 사이의 빈 공간을 채우는 기능입니다. "이건 컴포넌트로 분리하기엔 작은데, 반복되니까 추출하고 싶다" — 그런 순간에 Snippet이 빛납니다.
댓글 로딩 중...