Svelte集成 #
一、项目创建 #
1.1 使用脚手架创建 #
bash
# 创建 Svelte + Tauri 项目
npm create tauri-app@latest my-svelte-app
# 选择配置
✔ Project name · my-svelte-app
✔ Choose which language to use for your frontend · TypeScript / JavaScript
✔ Choose your package manager · pnpm
✔ Choose your UI template · Svelte
✔ Choose your UI flavor · TypeScript
1.2 项目结构 #
text
my-svelte-app/
├── src/
│ ├── lib/
│ │ ├── components/
│ │ │ ├── Header.svelte
│ │ │ └── Sidebar.svelte
│ │ └── stores/
│ │ └── app.ts
│ ├── routes/
│ │ ├── +page.svelte
│ │ └── settings/
│ │ └── +page.svelte
│ ├── app.html
│ └── app.css
├── src-tauri/
│ ├── src/
│ │ └── lib.rs
│ ├── Cargo.toml
│ └── tauri.conf.json
├── package.json
├── svelte.config.js
├── vite.config.ts
└── tsconfig.json
1.3 安装依赖 #
bash
# 安装 Tauri API
pnpm add @tauri-apps/api
# 安装常用插件
pnpm add @tauri-apps/plugin-shell
pnpm add @tauri-apps/plugin-dialog
pnpm add @tauri-apps/plugin-fs
二、基础配置 #
2.1 Vite 配置 #
typescript
// vite.config.ts
import { defineConfig } from 'vite';
import { sveltekit } from '@sveltejs/kit/vite';
export default defineConfig({
plugins: [sveltekit()],
clearScreen: false,
server: {
port: 1420,
strictPort: true,
},
envPrefix: ['VITE_', 'TAURI_'],
build: {
target: ['es2021', 'chrome100', 'safari13'],
minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
sourcemap: !!process.env.TAURI_DEBUG,
},
});
2.2 SvelteKit 配置 #
typescript
// svelte.config.js
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: 'index.html',
precompress: false,
strict: true,
}),
},
};
export default config;
三、工具函数 #
3.1 命令调用 #
typescript
// lib/utils/commands.ts
import { invoke } from '@tauri-apps/api/core';
import { writable } from 'svelte/store';
export function createCommand<T, P = void>(command: string) {
const data = writable<T | null>(null);
const loading = writable(false);
const error = writable<string | null>(null);
async function execute(params?: P) {
loading.set(true);
error.set(null);
try {
const result = await invoke<T>(command, params);
data.set(result);
return result;
} catch (err) {
error.set(String(err));
throw err;
} finally {
loading.set(false);
}
}
return { data, loading, error, execute };
}
3.2 事件监听 #
typescript
// lib/utils/events.ts
import { onMount, onDestroy } from 'svelte';
import { listen, UnlistenFn } from '@tauri-apps/api/event';
export function useEvent<T>(event: string, callback: (payload: T) => void) {
let unlisten: UnlistenFn | null = null;
onMount(async () => {
unlisten = await listen<T>(event, (e) => {
callback(e.payload);
});
});
onDestroy(() => {
if (unlisten) {
unlisten();
}
});
}
3.3 窗口状态 #
typescript
// lib/utils/window.ts
import { writable } from 'svelte/store';
import { onMount, onDestroy } from 'svelte';
import { getCurrentWindow } from '@tauri-apps/api/window';
export function useWindowState() {
const isMaximized = writable(false);
const window = getCurrentWindow();
let unlisten: (() => void) | null = null;
onMount(async () => {
isMaximized.set(await window.isMaximized());
unlisten = await window.onMaximized(({ payload }) => {
isMaximized.set(payload);
});
});
onDestroy(() => {
if (unlisten) {
unlisten();
}
});
async function toggleMaximize() {
await window.toggleMaximize();
}
async function minimize() {
await window.minimize();
}
async function close() {
await window.close();
}
return { isMaximized, toggleMaximize, minimize, close };
}
四、组件开发 #
4.1 标题栏组件 #
svelte
<script lang="ts">
import { useWindowState } from '../lib/utils/window';
const { isMaximized, toggleMaximize, minimize, close } = useWindowState();
</script>
<div class="titlebar" data-tauri-drag-region>
<div class="titlebar-title">My App</div>
<div class="titlebar-controls">
<button class="titlebar-button" on:click={minimize}>
<svg width="12" height="12" viewBox="0 0 12 12">
<rect y="5" width="12" height="2" />
</svg>
</button>
<button class="titlebar-button" on:click={toggleMaximize}>
{#if $isMaximized}
<svg width="12" height="12" viewBox="0 0 12 12">
<rect x="2" y="4" width="6" height="6" fill="none" stroke="currentColor" />
<path d="M4 4V2h6v6h-2" fill="none" stroke="currentColor" />
</svg>
{:else}
<svg width="12" height="12" viewBox="0 0 12 12">
<rect x="2" y="2" width="8" height="8" fill="none" stroke="currentColor" />
</svg>
{/if}
</button>
<button class="titlebar-button close" on:click={close}>
<svg width="12" height="12" viewBox="0 0 12 12">
<path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="2" />
</svg>
</button>
</div>
</div>
<style>
.titlebar {
display: flex;
justify-content: space-between;
align-items: center;
height: 32px;
background: #1e1e1e;
padding: 0 8px;
user-select: none;
}
.titlebar-title {
color: #fff;
font-size: 13px;
}
.titlebar-controls {
display: flex;
gap: 4px;
}
.titlebar-button {
width: 32px;
height: 24px;
border: none;
background: transparent;
color: #fff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.titlebar-button:hover {
background: rgba(255, 255, 255, 0.1);
}
.titlebar-button.close:hover {
background: #e81123;
}
</style>
4.2 文件选择组件 #
svelte
<script lang="ts">
import { open } from '@tauri-apps/plugin-dialog';
export let filters: { name: string; extensions: string[] }[] = [
{ name: 'All Files', extensions: ['*'] }
];
let selectedPath: string | null = null;
async function handleOpen() {
const selected = await open({
multiple: false,
filters,
});
if (selected) {
selectedPath = selected as string;
dispatch('fileSelected', selectedPath);
}
}
const dispatch = createEventDispatcher();
</script>
<div class="file-picker">
<button on:click={handleOpen}>选择文件</button>
{#if selectedPath}
<span class="selected-path">{selectedPath}</span>
{/if}
</div>
4.3 通知组件 #
svelte
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { listen } from '@tauri-apps/api/event';
interface NotificationData {
title: string;
message: string;
type: 'info' | 'success' | 'error';
}
let notification: NotificationData | null = null;
let timeout: ReturnType<typeof setTimeout> | null = null;
let unlisten: (() => void) | null = null;
onMount(async () => {
unlisten = await listen<NotificationData>('notification', (event) => {
notification = event.payload;
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
notification = null;
}, 3000);
});
});
onDestroy(() => {
if (unlisten) {
unlisten();
}
if (timeout) {
clearTimeout(timeout);
}
});
</script>
{#if notification}
<div class="notification notification-{notification.type}">
<strong>{notification.title}</strong>
<p>{notification.message}</p>
</div>
{/if}
<style>
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 16px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.notification-info {
background: #e6f7ff;
border: 1px solid #1890ff;
}
.notification-success {
background: #f6ffed;
border: 1px solid #52c41a;
}
.notification-error {
background: #fff2f0;
border: 1px solid #ff4d4f;
}
</style>
五、状态管理 #
5.1 Svelte Store #
typescript
// lib/stores/app.ts
import { writable, derived } from 'svelte/store';
import { invoke } from '@tauri-apps/api/core';
interface User {
id: number;
name: string;
}
function createAppStore() {
const user = writable<User | null>(null);
const theme = writable<'light' | 'dark'>('light');
return {
user,
theme,
async loadUser() {
const result = await invoke<User>('get_current_user');
user.set(result);
},
setTheme(newTheme: 'light' | 'dark') {
theme.set(newTheme);
},
};
}
export const appStore = createAppStore();
5.2 使用 Store #
svelte
<script lang="ts">
import { onMount } from 'svelte';
import { appStore } from '../lib/stores/app';
onMount(() => {
appStore.loadUser();
});
</script>
<div class="app {$appStore.theme}">
{#if $appStore.user}
<h1>Welcome, {$appStore.user.name}</h1>
{/if}
</div>
六、路由 #
6.1 SvelteKit 路由 #
text
src/routes/
├── +page.svelte # 首页 /
├── +layout.svelte # 布局
├── settings/
│ └── +page.svelte # 设置页 /settings
└── about/
└── +page.svelte # 关于页 /about
6.2 布局文件 #
svelte
<script lang="ts">
import TitleBar from '../lib/components/TitleBar.svelte';
</script>
<TitleBar />
<nav>
<a href="/">Home</a>
<a href="/settings">Settings</a>
<a href="/about">About</a>
</nav>
<main>
<slot />
</main>
七、最佳实践 #
7.1 组件 Props #
svelte
<script lang="ts">
interface Props {
title: string;
count?: number;
items: string[];
}
let { title, count = 0, items }: Props = $props();
</script>
7.2 组件事件 #
svelte
<script lang="ts">
interface Events {
click: MouseEvent;
update: { value: string };
}
let { onclick, onupdate }: Events = $props();
function handleClick(e: MouseEvent) {
onclick?.(e);
onupdate?.({ value: 'new value' });
}
</script>
7.3 响应式声明 #
svelte
<script lang="ts">
let count = $state(0);
let doubled = $derived(count * 2);
function increment() {
count++;
}
</script>
<button onclick={increment}>
Count: {count} (doubled: {doubled})
</button>
八、总结 #
8.1 核心要点 #
| 要点 | 说明 |
|---|---|
| 工具函数 | 封装 Tauri API |
| 组件开发 | 使用 Svelte 语法 |
| 状态管理 | 使用 Svelte Store |
| 路由配置 | 使用 SvelteKit |
8.2 下一步 #
现在你已经掌握了 Svelte 集成,接下来让我们学习 TypeScript支持,了解如何在 Tauri 项目中使用 TypeScript!
最后更新:2026-03-28