lang="ts"를 추가하는 순간, 컴파일러가 버그를 잡아주기 시작합니다.

개념 정의

Svelte는 TypeScript를 1등 시민으로 지원합니다. <script lang="ts">를 추가하면 컴포넌트에서 타입 검사를 사용할 수 있고, SvelteKit은 라우트 파라미터와 load 함수의 타입을 자동 생성합니다.

컴포넌트에서 TypeScript

SVELTE
<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/ 디렉토리에 라우트별 타입을 자동 생성합니다.

TYPESCRIPT
// 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 }) => {
    // 타입 안전한 액션
  }
};
SVELTE
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';

  let { data }: { data: PageData } = $props();
  // data.post의 타입이 load 반환값에서 자동 추론됨
</script>

이벤트 타입

SVELTE
<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>

제네릭 컴포넌트

SVELTE
<!-- 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>
SVELTE
<!-- 사용 — 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 — 전역 타입

TYPESCRIPT
// 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의 자동 타입 생성까지 활용하면, 전체 앱의 데이터 흐름을 타입으로 추적할 수 있습니다.

댓글 로딩 중...