TypeScript — Svelte에서 타입 안전하게 개발하기
lang="ts"를 추가하는 순간, 컴파일러가 버그를 잡아주기 시작합니다.
개념 정의
Svelte는 TypeScript를 1등 시민으로 지원합니다. <script lang="ts">를 추가하면 컴포넌트에서 타입 검사를 사용할 수 있고, SvelteKit은 라우트 파라미터와 load 함수의 타입을 자동 생성합니다.
컴포넌트에서 TypeScript
<script lang="ts">
// Props 타입 정의
interface Props {
name: string;
age?: number;
role: 'admin' | 'user' | 'editor';
onSelect?: (id: string) => void;
}
let { name, age = 0, role, onSelect }: Props = $props();
// 상태 타입
let count: number = $state(0);
let items: string[] = $state([]);
// 제네릭 타입 추론
interface User {
id: string;
name: string;
email: string;
}
let users: User[] = $state([]);
let selectedUser: User | null = $state(null);
</script>
SvelteKit 자동 타입 생성
SvelteKit은 .svelte-kit/types/ 디렉토리에 라우트별 타입을 자동 생성합니다.
// src/routes/blog/[slug]/+page.server.ts
import type { PageServerLoad, Actions } from './$types';
export const load: PageServerLoad = async ({ params }) => {
// params.slug의 타입이 자동 추론됨
const post = await getPost(params.slug);
return { post };
};
export const actions: Actions = {
update: async ({ request, params }) => {
// 타입 안전한 액션
}
};
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
// data.post의 타입이 load 반환값에서 자동 추론됨
</script>
이벤트 타입
<script lang="ts">
function handleInput(e: Event & { currentTarget: HTMLInputElement }) {
console.log(e.currentTarget.value);
}
function handleSubmit(e: SubmitEvent) {
e.preventDefault();
const formData = new FormData(e.currentTarget as HTMLFormElement);
}
function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Enter') {
console.log('엔터 입력');
}
}
</script>
<input oninput={handleInput} onkeydown={handleKeydown} />
<form onsubmit={handleSubmit}>
<button type="submit">제출</button>
</form>
제네릭 컴포넌트
<!-- Select.svelte -->
<script lang="ts" generics="T">
interface Props {
items: T[];
selected: T | null;
getLabel: (item: T) => string;
getValue: (item: T) => string;
onSelect: (item: T) => void;
}
let { items, selected, getLabel, getValue, onSelect }: Props = $props();
</script>
<select onchange={(e) => {
const item = items.find(i => getValue(i) === e.currentTarget.value);
if (item) onSelect(item);
}}>
{#each items as item}
<option
value={getValue(item)}
selected={selected && getValue(item) === getValue(selected)}
>
{getLabel(item)}
</option>
{/each}
</select>
<!-- 사용 — T가 User로 추론됨 -->
<script lang="ts">
import Select from './Select.svelte';
interface User { id: string; name: string; }
let users: User[] = $state([]);
let selected: User | null = $state(null);
</script>
<Select
items={users}
{selected}
getLabel={(u) => u.name}
getValue={(u) => u.id}
onSelect={(u) => selected = u}
/>
app.d.ts — 전역 타입
// src/app.d.ts
declare global {
namespace App {
interface Locals {
user: { id: string; name: string; role: string } | null;
}
interface PageData {
user: { id: string; name: string } | null;
}
interface Error {
message: string;
code?: string;
}
}
}
export {};
면접 포인트
- "Svelte에서 TypeScript를 쓰는 장점은?": Props 타입이 컴파일 타임에 검증되고, SvelteKit의 자동 타입 생성으로 load 함수와 페이지 간 데이터 흐름이 타입 안전해집니다.
- "제네릭 컴포넌트는 어떻게 만드나요?":
generics속성을 script 태그에 추가하면 컴포넌트 레벨 제네릭을 선언할 수 있습니다.
정리
TypeScript와 Svelte의 조합은 "컴파일러가 두 번 검증하는" 안전망입니다. Svelte 컴파일러가 마크업을 검증하고, TypeScript가 로직을 검증합니다. SvelteKit의 자동 타입 생성까지 활용하면, 전체 앱의 데이터 흐름을 타입으로 추적할 수 있습니다.
댓글 로딩 중...