Svelte与TypeScript #

一、TypeScript 配置 #

1.1 启用 TypeScript #

创建项目时选择 TypeScript:

bash
npm create svelte@latest my-app -- --template skeleton --types typescript

1.2 tsconfig.json #

json
{
  "extends": "./.svelte-kit/tsconfig.json",
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "strict": true,
    "moduleResolution": "bundler",
    "module": "ESNext",
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUncheckedIndexedAccess": true
  }
}

1.3 组件中使用 TypeScript #

svelte
<script lang="ts">
  let count: number = 0;
  let name: string = 'Svelte';
  let items: string[] = [];
  let user: { name: string; age: number } | null = null;
</script>

二、Props 类型定义 #

2.1 Svelte 5 Props 类型 #

svelte
<script lang="ts">
  interface Props {
    title: string;
    count?: number;
    items: string[];
    onUpdate?: (value: number) => void;
  }
  
  let { 
    title, 
    count = 0, 
    items, 
    onUpdate 
  }: Props = $props();
</script>

<h1>{title}</h1>
<p>Count: {count}</p>

2.2 可选 Props #

svelte
<script lang="ts">
  interface Props {
    required: string;
    optional?: number;
    withDefault?: string;
  }
  
  let { 
    required, 
    optional, 
    withDefault = 'default value' 
  }: Props = $props();
</script>

2.3 复杂 Props 类型 #

svelte
<script lang="ts">
  interface User {
    id: string;
    name: string;
    email: string;
    role: 'admin' | 'user' | 'guest';
  }
  
  interface Props {
    user: User;
    onUserUpdate: (user: User) => void;
    onUserDelete: (id: string) => void;
  }
  
  let { user, onUserUpdate, onUserDelete }: Props = $props();
</script>

2.4 Snippet 类型 #

svelte
<script lang="ts">
  import type { Snippet } from 'svelte';
  
  interface Props {
    header?: Snippet;
    footer?: Snippet;
    children: Snippet;
  }
  
  let { header, footer, children }: Props = $props();
</script>

{#if header}
  <header>{@render header()}</header>
{/if}

<main>{@render children()}</main>

{#if footer}
  <footer>{@render footer()}</footer>
{/if}

2.5 带参数的 Snippet #

svelte
<script lang="ts">
  import type { Snippet } from 'svelte';
  
  interface Item {
    id: string;
    name: string;
  }
  
  interface Props {
    items: Item[];
    renderItem: Snippet<[Item, number]>;
  }
  
  let { items, renderItem }: Props = $props();
</script>

{#each items as item, index}
  {@render renderItem(item, index)}
{/each}

三、响应式类型 #

3.1 $state 类型 #

svelte
<script lang="ts">
  let count = $state<number>(0);
  let name = $state<string>('Svelte');
  let items = $state<string[]>([]);
  let user = $state<User | null>(null);
</script>

3.2 $derived 类型 #

svelte
<script lang="ts">
  let count = $state(0);
  
  let doubled: number = $derived(count * 2);
  let message: string = $derived(`Count is ${count}`);
  
  let user = $state<User | null>(null);
  let userName: string = $derived(user?.name ?? 'Guest');
</script>

3.3 $derived.by 类型 #

svelte
<script lang="ts">
  interface Statistics {
    total: number;
    average: number;
    max: number;
    min: number;
  }
  
  let numbers = $state<number[]>([]);
  
  let stats: Statistics = $derived.by(() => {
    if (numbers.length === 0) {
      return { total: 0, average: 0, max: 0, min: 0 };
    }
    
    const total = numbers.reduce((a, b) => a + b, 0);
    
    return {
      total,
      average: total / numbers.length,
      max: Math.max(...numbers),
      min: Math.min(...numbers)
    };
  });
</script>

四、事件类型 #

4.1 DOM 事件类型 #

svelte
<script lang="ts">
  function handleClick(event: MouseEvent): void {
    console.log(event.clientX, event.clientY);
  }
  
  function handleKeyDown(event: KeyboardEvent): void {
    if (event.key === 'Enter') {
      console.log('Enter pressed');
    }
  }
  
  function handleInput(event: Event): void {
    const target = event.target as HTMLInputElement;
    console.log(target.value);
  }
</script>

<button onclick={handleClick}>Click</button>
<input onkeydown={handleKeyDown} oninput={handleInput} />

4.2 自定义事件类型 #

svelte
<script lang="ts">
  interface CustomEventDetail {
    id: string;
    value: number;
  }
  
  function handleCustomEvent(event: CustomEvent<CustomEventDetail>): void {
    console.log(event.detail.id, event.detail.value);
  }
</script>

4.3 表单事件 #

svelte
<script lang="ts">
  interface FormData {
    email: string;
    password: string;
    remember: boolean;
  }
  
  function handleSubmit(event: SubmitEvent): void {
    event.preventDefault();
    const form = event.target as HTMLFormElement;
    const formData = new FormData(form);
    
    const data: FormData = {
      email: formData.get('email') as string,
      password: formData.get('password') as string,
      remember: formData.get('remember') === 'on'
    };
    
    console.log(data);
  }
</script>

<form onsubmit={handleSubmit}>
  <input type="email" name="email" />
  <input type="password" name="password" />
  <input type="checkbox" name="remember" />
  <button type="submit">Submit</button>
</form>

五、Store 类型 #

5.1 Writable Store 类型 #

typescript
import { writable, type Writable } from 'svelte/store';

interface User {
  id: string;
  name: string;
  email: string;
}

export const user: Writable<User | null> = writable(null);

export const count: Writable<number> = writable(0);

5.2 Derived Store 类型 #

typescript
import { derived, type Readable } from 'svelte/store';

export const doubleCount: Readable<number> = derived(
  count,
  $count => $count * 2
);

export const userDisplayName: Readable<string> = derived(
  user,
  $user => $user?.name ?? 'Guest'
);

5.3 自定义 Store 类型 #

typescript
import { writable, type Readable } from 'svelte/store';

interface Todo {
  id: string;
  text: string;
  done: boolean;
}

interface TodoStore extends Readable<Todo[]> {
  add: (text: string) => void;
  remove: (id: string) => void;
  toggle: (id: string) => void;
}

function createTodoStore(): TodoStore {
  const { subscribe, set, update } = writable<Todo[]>([]);
  
  return {
    subscribe,
    add: (text: string) => {
      update(todos => [...todos, { id: crypto.randomUUID(), text, done: false }]);
    },
    remove: (id: string) => {
      update(todos => todos.filter(t => t.id !== id));
    },
    toggle: (id: string) => {
      update(todos => todos.map(t => 
        t.id === id ? { ...t, done: !t.done } : t
      ));
    }
  };
}

export const todos = createTodoStore();

六、Action 类型 #

6.1 Action 类型定义 #

typescript
import type { Action } from 'svelte/action';

interface TooltipOptions {
  text: string;
  position?: 'top' | 'bottom' | 'left' | 'right';
}

export const tooltip: Action<HTMLElement, TooltipOptions> = (
  node,
  options
) => {
  // Implementation
  
  return {
    update(newOptions) {
      // Update logic
    },
    destroy() {
      // Cleanup
    }
  };
};

6.2 带事件的 Action #

typescript
import type { Action } from 'svelte/action';

interface LongpressOptions {
  duration?: number;
}

interface LongpressAttributes {
  'on:longpress'?: (event: CustomEvent) => void;
}

export const longpress: Action<
  HTMLElement, 
  LongpressOptions, 
  LongpressAttributes
> = (node, { duration = 500 } = {}) => {
  let timer: number;
  
  function handleMouseDown() {
    timer = setTimeout(() => {
      node.dispatchEvent(new CustomEvent('longpress'));
    }, duration);
  }
  
  function handleMouseUp() {
    clearTimeout(timer);
  }
  
  node.addEventListener('mousedown', handleMouseDown);
  node.addEventListener('mouseup', handleMouseUp);
  node.addEventListener('mouseleave', handleMouseUp);
  
  return {
    update({ duration: newDuration = 500 }) {
      duration = newDuration;
    },
    destroy() {
      node.removeEventListener('mousedown', handleMouseDown);
      node.removeEventListener('mouseup', handleMouseUp);
      node.removeEventListener('mouseleave', handleMouseUp);
    }
  };
};

七、SvelteKit 类型 #

7.1 Page Data 类型 #

src/routes/blog/[slug]/+page.ts

typescript
import type { PageLoad } from './$types';

interface Post {
  id: string;
  title: string;
  content: string;
}

export const load: PageLoad = async ({ params, fetch }) => {
  const response = await fetch(`/api/posts/${params.slug}`);
  const post: Post = await response.json();
  
  return { post };
};

src/routes/blog/[slug]/+page.svelte

svelte
<script lang="ts">
  import type { PageData } from './$types';
  
  let { data }: { data: PageData } = $props();
</script>

<h1>{data.post.title}</h1>
<p>{data.post.content}</p>

7.2 Layout Data 类型 #

src/routes/+layout.ts

typescript
import type { LayoutLoad } from './$types';

export const load: LayoutLoad = async () => {
  return {
    siteName: 'My App'
  };
};

src/routes/+layout.svelte

svelte
<script lang="ts">
  import type { LayoutData } from './$types';
  
  let { data, children }: { data: LayoutData; children: import('svelte').Snippet } = $props();
</script>

<h1>{data.siteName}</h1>
{@render children()}

7.3 Server Load 类型 #

src/routes/+page.server.ts

typescript
import type { PageServerLoad } from './$types';
import type { Actions } from './$types';

export const load: PageServerLoad = async ({ cookies }) => {
  const session = cookies.get('session');
  
  return { session };
};

export const actions: Actions = {
  default: async ({ request, cookies }) => {
    const formData = await request.formData();
    
    return { success: true };
  }
};

八、类型声明文件 #

8.1 全局类型声明 #

src/app.d.ts

typescript
declare global {
  namespace App {
    interface Error {
      code: string;
      message: string;
    }
    
    interface Locals {
      user: User | null;
      sessionId: string | null;
    }
    
    interface PageData {
      // ...
    }
    
    interface PageState {
      // ...
    }
    
    interface Platform {
      // ...
    }
  }
}

export {};

8.2 模块扩展 #

src/types/svelte.d.ts

typescript
declare module '*.svelte' {
  import type { SvelteComponent } from 'svelte';
  
  export default SvelteComponent;
}

九、最佳实践 #

9.1 类型导入 #

typescript
// 推荐:使用 type 关键字
import type { User, Post } from '$lib/types';
import { formatDate } from '$lib/utils';

// 或使用内联类型导入
import { type User, type Post, formatDate } from '$lib';

9.2 严格空值检查 #

svelte
<script lang="ts">
  let user = $state<User | null>(null);
  
  // 使用可选链
  let name = $derived(user?.name ?? 'Guest');
  
  // 使用类型守卫
  function getDisplayName(): string {
    if (user === null) return 'Guest';
    return user.name;
  }
</script>

9.3 泛型组件 #

svelte
<script lang="ts" generics="T extends { id: string }">
  interface Props {
    items: T[];
    onSelect: (item: T) => void;
    renderItem: Snippet<[T]>;
  }
  
  let { items, onSelect, renderItem }: Props = $props();
</script>

{#each items as item (item.id)}
  <button onclick={() => onSelect(item)}>
    {@render renderItem(item)}
  </button>
{/each}

十、总结 #

类型 说明
interface Props 组件 Props 类型
$state<T>() 响应式状态类型
$derived 派生状态类型
PageData 页面数据类型
LayoutData 布局数据类型
Action Action 类型

TypeScript 要点:

  • 使用 lang="ts" 启用 TypeScript
  • 定义 Props 接口
  • 使用类型安全的 Store
  • 正确处理事件类型
  • 利用 SvelteKit 自动生成的类型
  • 启用严格模式
最后更新:2026-03-28