Props와 이벤트 — 부모-자식 컴포넌트 통신의 모든 것
Svelte 5의
$props()는 구조 분해 한 줄로 모든 props를 받습니다 —export let보다 훨씬 깔끔합니다.
개념 정의
Props(Properties) 는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 단방향 데이터 흐름입니다. Svelte 5에서는 $props() rune으로 props를 선언합니다.
기본 Props 선언
<!-- Greeting.svelte -->
<script>
// 구조 분해로 props 받기
let { name, age } = $props();
</script>
<p>{name}님 ({age}세), 환영합니다!</p>
<!-- App.svelte -->
<script>
import Greeting from './Greeting.svelte';
</script>
<Greeting name="홍길동" age={25} />
기본값 설정
<script>
let {
name = '게스트', // 기본값: '게스트'
variant = 'primary', // 기본값: 'primary'
size = 'md', // 기본값: 'md'
disabled = false, // 기본값: false
} = $props();
</script>
<button class="btn btn-{variant} btn-{size}" {disabled}>
{name}
</button>
나머지 Props (Rest Props)
<!-- Input.svelte -->
<script>
// 특정 props만 빼고 나머지는 그대로 전달
let { label, error, ...restProps } = $props();
</script>
<div class="field">
{#if label}
<label>{label}</label>
{/if}
<!-- 나머지 props를 input에 전달 -->
<input {...restProps} class:error />
{#if error}
<span class="error-msg">{error}</span>
{/if}
</div>
<style>
.error { border-color: red; }
.error-msg { color: red; font-size: 0.8rem; }
</style>
<!-- 사용 -->
<Input
label="이메일"
type="email"
placeholder="example@email.com"
required
error={emailError}
/>
콜백 Props로 자식 → 부모 통신
<!-- SearchBar.svelte -->
<script>
let { onSearch, onClear, placeholder = '검색...' } = $props();
let query = $state('');
function handleSubmit(e) {
e.preventDefault();
onSearch?.(query); // 옵셔널 체이닝으로 안전하게 호출
}
</script>
<form onsubmit={handleSubmit}>
<input bind:value={query} {placeholder} />
<button type="submit">검색</button>
{#if query}
<button type="button" onclick={() => { query = ''; onClear?.(); }}>
초기화
</button>
{/if}
</form>
<!-- App.svelte -->
<script>
import SearchBar from './SearchBar.svelte';
let results = $state([]);
async function handleSearch(query) {
const res = await fetch(`/api/search?q=${query}`);
results = await res.json();
}
function handleClear() {
results = [];
}
</script>
<SearchBar onSearch={handleSearch} onClear={handleClear} />
타입 안전한 Props (TypeScript)
<!-- UserCard.svelte -->
<script lang="ts">
interface Props {
name: string;
email: string;
avatar?: string;
role?: 'admin' | 'user' | 'editor';
onSelect?: (id: string) => void;
}
let {
name,
email,
avatar = '/default-avatar.png',
role = 'user',
onSelect
}: Props = $props();
</script>
<div class="card" onclick={() => onSelect?.(email)}>
<img src={avatar} alt={name} />
<h3>{name}</h3>
<p>{email}</p>
<span class="badge badge-{role}">{role}</span>
</div>
Props 반응성
$props()로 받은 값은 자동으로 반응형 입니다. 부모에서 값이 바뀌면 자식도 자동 업데이트됩니다.
<!-- Counter.svelte -->
<script>
let { initialCount = 0 } = $props();
// props를 로컬 상태로 복사해서 사용
let count = $state(initialCount);
</script>
<button onclick={() => count++}>
초기값: {initialCount}, 현재: {count}
</button>
<!-- App.svelte -->
<script>
import Counter from './Counter.svelte';
let start = $state(0);
</script>
<input type="number" bind:value={start} />
<Counter initialCount={start} />
**주의 **: props 값을 로컬 $state에 복사하면, 이후 부모에서 변경해도 로컬 상태는 업데이트되지 않습니다. 동기화가 필요하면 $derived나 $effect를 사용해야 합니다.
실전 패턴 — 컴포넌트 컴포지션
<!-- DataTable.svelte -->
<script>
let {
data = [],
columns = [],
onRowClick,
sortable = false,
emptyMessage = '데이터가 없습니다',
} = $props();
let sortKey = $state('');
let sortDir = $state('asc');
let sortedData = $derived.by(() => {
if (!sortKey) return data;
return [...data].sort((a, b) => {
const mul = sortDir === 'asc' ? 1 : -1;
return String(a[sortKey]).localeCompare(String(b[sortKey])) * mul;
});
});
function toggleSort(key) {
if (sortKey === key) {
sortDir = sortDir === 'asc' ? 'desc' : 'asc';
} else {
sortKey = key;
sortDir = 'asc';
}
}
</script>
<table>
<thead>
<tr>
{#each columns as col}
<th onclick={() => sortable && toggleSort(col.key)}>
{col.label}
{#if sortKey === col.key}
{sortDir === 'asc' ? '▲' : '▼'}
{/if}
</th>
{/each}
</tr>
</thead>
<tbody>
{#each sortedData as row}
<tr onclick={() => onRowClick?.(row)}>
{#each columns as col}
<td>{row[col.key]}</td>
{/each}
</tr>
{:else}
<tr><td colspan={columns.length}>{emptyMessage}</td></tr>
{/each}
</tbody>
</table>
면접 포인트
- "Svelte 5에서 export let이 사라진 이유는?":
export let은 JavaScript 표준에서 벗어난 문법이었고, 어떤 것이 prop인지 코드에서 명확하지 않았습니다.$props()는 명시적이고 구조 분해와 자연스럽게 결합됩니다. - "단방향 vs 양방향 데이터 흐름?": 기본 Props는 단방향(부모→자식)입니다. 양방향이 필요하면
$bindable을 사용하지만, 대부분은 콜백 props로 자식→부모 통신을 처리하는 것이 데이터 흐름 추적에 유리합니다.
정리
Props는 컴포넌트 간 통신의 기본입니다. Svelte 5의 $props()는 구조 분해, 기본값, rest props를 JavaScript 표준 문법 그대로 사용하게 해줍니다. 콜백 props 패턴까지 익히면 대부분의 부모-자식 통신 시나리오를 깔끔하게 처리할 수 있습니다.
댓글 로딩 중...