> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Svelte 入门 —— 与 Inertia × Laravel 搭配使用的基础

> 在 Laravel + Inertia.js 中使用 Svelte 的入门指南。从编译器思路和 Svelte 5 的 runes 语法,到 useForm、usePage 等 Inertia Svelte hook,面向实战讲解。

## 什么是 Svelte

[Svelte](https://svelte.jp/) 在 JavaScript 框架中占据了非常独特的一席之地。React、Vue 是**运行时库**,而 Svelte 是**编译器**。它在构建时把组件编译为纯 JavaScript,因此浏览器无需接收额外的框架代码。

Svelte 最鲜明的特点是**不使用虚拟 DOM**。状态发生变化时,Svelte 在编译时生成的代码会直接更新 DOM。因此可以做到非常轻量、高速的 UI。

<Info>
  本文讲解的是 Svelte 5 与 Inertia v3 的组合。Laravel 13 的启动套件默认使用这一组合。
</Info>

### Svelte 5 的 runes

Svelte 5(2024 年发布)引入了名为 **Runes** 的新响应式系统。使用 `$state`、`$derived`、`$effect` 等特殊函数(runes)来声明响应式状态。相比 Svelte 4 之前的隐式响应式,它更加显式、更易预测。

```svelte theme={null}
<script lang="ts">
    let count = $state(0)

    function increment() {
        count++
    }
</script>

<button onclick={increment}>{count}</button>
```

<Tip>
  Laravel 的 Svelte 启动套件默认使用 Svelte 5 + TypeScript。本文示例也都用 TypeScript(`lang="ts"`)。
</Tip>

***

## 在 Laravel 中的定位

### 历史

相较 Vue 和 React,Svelte 与 Laravel 的关系非常新,**被官方启动套件采纳**才是它的第一次官方支持。

```mermaid theme={null}
timeline
    title Laravel 与 Svelte 的历程
    2016 : Svelte 1 发布(作者 Rich Harris)
    2019 : Svelte 3 —— 重构为编译器为核心的设计
    2021 : Inertia.js —— 社区提供 Svelte 适配器
    2024 : Svelte 5 —— 引入 runes 语法
    2026 : Laravel 13 —— 官方新增 Svelte 启动套件(支持 Inertia v3)
```

**2026 年的 Laravel 13** 在启动套件中正式加入 Svelte,让 Svelte 成为 Laravel 生态中的官方前端选项之一。它和 Vue、React 平起平坐,可以在 `laravel new` 的交互式提示中选择。

对不少 Laravel 用户来说 Svelte 还不算熟悉,但它以**编译带来的轻量包体积**和**简洁语法**著称,从 Vue 或 React 迁移过来,你可能会惊讶“居然要写这么少”。

### 当前主流:Inertia × Svelte

当前 Laravel 中使用 Svelte 的主流方式就是 **Inertia × Svelte**。Inertia 让你不必设计 API,就能直接从 Laravel 控制器把数据传给 Svelte 组件,实现“现代单体”架构。

```mermaid theme={null}
graph LR
    Browser["浏览器"]
    Inertia["Inertia.js<br>(适配器层)"]
    Laravel["Laravel<br>(控制器)"]
    Svelte["Svelte<br>(页面组件)"]

    Browser <-->|XHR / 整页加载| Inertia
    Inertia <-->|Inertia 响应| Laravel
    Inertia -->|props| Svelte
    Svelte -->|渲染| Browser
```

***

## 搭建

### 通过启动套件(推荐)

新项目最省事的方式就是使用启动套件。

```shell theme={null}
laravel new my-app
```

在交互式提示中选择 **Svelte**,以下内容会全部自动配好。

* `inertiajs/inertia-laravel`(服务端适配器)
* `@inertiajs/svelte`(客户端适配器)
* `svelte` + `@sveltejs/vite-plugin-svelte`(Svelte 5 本体及 Vite 插件)
* TypeScript + `svelte-check`
* Tailwind CSS + shadcn-svelte 组件库
* `HandleInertiaRequests` 中间件
* 登录 / 注册等认证页面(已用 Inertia + Svelte + TypeScript 实现)

### 手动安装

在已有项目中引入时,分别安装服务端和客户端。

```shell theme={null}
# 服务端(PHP)
composer require inertiajs/inertia-laravel

# 客户端(JavaScript)
npm install @inertiajs/svelte @inertiajs/vite svelte
npm install --save-dev @sveltejs/vite-plugin-svelte svelte-check typescript
```

然后在 `vite.config.ts` 里加上 Svelte 插件和 Inertia Vite 插件。

```ts theme={null}
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import inertia from '@inertiajs/vite'

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.ts'],
            refresh: true,
        }),
        svelte(),
        inertia(),
    ],
})
```

在 `resources/js/app.ts` 里启动 Inertia 应用。`@inertiajs/vite` 插件会负责页面的自动解析和挂载,入口写得非常精简。

```ts theme={null}
import { createInertiaApp } from '@inertiajs/svelte'

createInertiaApp()
```

<Info>
  手动安装的详细步骤(根模板设置、中间件注册等)请参考 [Inertia 官方文档](https://inertiajs.com/installation)。
</Info>

***

## 目录结构

启动套件把 Svelte 页面组件放在 `resources/js/pages/` 下。

```
resources/js/
├── app.ts             # Inertia 应用入口
├── components/        # 可复用 UI 组件
│   └── ui/            # shadcn-svelte 组件
├── layouts/           # 布局组件
│   ├── AppLayout.svelte
│   └── AuthLayout.svelte
├── lib/               # 工具函数 / Svelte runes 模块
├── pages/             # Inertia 页面组件(对应控制器名)
│   ├── auth/
│   │   ├── Login.svelte
│   │   └── Register.svelte
│   ├── Dashboard.svelte
│   └── posts/
│       ├── Index.svelte
│       ├── Create.svelte
│       └── Show.svelte
└── types/             # TypeScript 类型定义
```

写 `Inertia::render('posts/Index', [...])` 时对应的就是 `resources/js/pages/posts/Index.svelte`。

***

## Svelte 文件的基本结构

`.svelte` 文件由 **`<script>`、模板、`<style>`** 三块组成。

```svelte theme={null}
<script lang="ts">
    // 逻辑(TypeScript)
    let name = $state('Laravel')
</script>

<!-- 模板(类 HTML 语法) -->
<h1>Hello, {name}!</h1>

<style>
    /* 作用域 CSS(只对该组件生效) */
    h1 {
        color: #ff2d20;
    }
</style>
```

结构与 Vue 的 SFC 相似,但模板和脚本的书写量更少。`<style>` 默认作用域受限,不用担心类名冲突。

### 模板语法

#### 变量展开与表达式

在模板中用 `{}` 嵌入 JavaScript 的值或表达式。

```svelte theme={null}
<script lang="ts">
    let name = $state('世界')
    let count = $state(3)
</script>

<p>你好,{name}!</p>
<p>2 倍是 {count * 2}</p>
```

#### `{#if}` —— 条件分支

```svelte theme={null}
<script lang="ts">
    let isLoggedIn = $state(false)
    let role = $state('editor')
</script>

{#if isLoggedIn}
    <p>欢迎!</p>
{:else if role === 'admin'}
    <p>正在以管理员身份登录</p>
{:else}
    <a href="/login">登录</a>
{/if}
```

相当于 Vue 的 `v-if` / `v-else`,或 React 中的三元运算符。

#### `{#each}` —— 列表渲染

```svelte theme={null}
<script lang="ts">
    type Post = { id: number; title: string }
    let posts = $state<Post[]>([
        { id: 1, title: '第一篇文章' },
        { id: 2, title: '第二篇文章' },
    ])
</script>

<ul>
    {#each posts as post (post.id)}
        <li>{post.title}</li>
    {/each}
</ul>
```

`(post.id)` 是键,用于高效的差异更新。相当于 Vue 的 `v-for` 或 React 的 `Array.map()`。

#### `bind:` —— 双向绑定

用 `bind:value` 可以让表单元素的值和响应式变量双向同步。

```svelte theme={null}
<script lang="ts">
    let title = $state('')
    let agreed = $state(false)
    let role = $state('viewer')
</script>

<!-- 文本输入 -->
<input bind:value={title} type="text" />
<p>输入中: {title}</p>

<!-- 复选框 -->
<input bind:checked={agreed} type="checkbox" />
<p>同意: {agreed}</p>

<!-- 下拉框 -->
<select bind:value={role}>
    <option value="viewer">查看者</option>
    <option value="editor">编辑者</option>
    <option value="admin">管理员</option>
</select>
```

相当于 Vue 的 `v-model`。React 里通常需要手写 `onChange`,Svelte 里用 `bind:` 就能声明式表达。

***

## 页面组件基础

Inertia 的页面组件就是普通的 Svelte 组件。从 Laravel 控制器传来的数据会以 props 的形式接收。

### 控制器

```php theme={null}
// app/Http/Controllers/PostController.php
use Inertia\Inertia;
use App\Models\Post;

class PostController extends Controller
{
    public function index()
    {
        return Inertia::render('posts/Index', [
            'posts' => Post::latest()->paginate(10),
        ]);
    }
}
```

### Svelte 页面组件

在 Svelte 5 里用 `$props()` rune 接收 props。

```svelte theme={null}
<!-- resources/js/pages/posts/Index.svelte -->
<script lang="ts">
    import { Link } from '@inertiajs/svelte'

    type Post = {
        id: number
        title: string
        created_at: string
    }

    type Props = {
        posts: {
            data: Post[]
        }
    }

    let { posts }: Props = $props()
</script>

<div>
    <h1>文章列表</h1>
    {#each posts.data as post (post.id)}
        <article>
            <h2>
                <Link href={`/posts/${post.id}`}>{post.title}</Link>
            </h2>
            <p>{post.created_at}</p>
        </article>
    {/each}
</div>
```

只要用 `$props()` 接收 props,就可以在模板里直接使用控制器传来的数据。无需定义 REST API。

***

## `Link` 组件

使用 `@inertiajs/svelte` 提供的 `<Link>` 组件,页面跳转通过 XHR 完成,避免整页重载。

```svelte theme={null}
<script lang="ts">
    import { Link } from '@inertiajs/svelte'
</script>

<!-- 基础链接 -->
<Link href="/posts">文章列表</Link>

<!-- 用 POST 方法的链接(如删除) -->
<Link href="/posts/1" method="delete" as="button">
    删除
</Link>

<!-- 预加载(hover 时预取) -->
<Link href="/posts/1" preload>查看文章</Link>
```

写法和普通 `<a>` 一样,但幕后 Inertia 只替换页面组件,体验接近 SPA。

***

## `Form` 组件

`@inertiajs/svelte` 提供的 `<Form>` 组件是启动套件认证页面推荐的表单提交样式。通过 props 指定 `action` 和 `method`,内部用 **`{#snippet}`** 写内容。

### 基本用法

```svelte theme={null}
<script lang="ts">
    import { Form } from '@inertiajs/svelte'
</script>

<Form action="/posts" method="post" class="flex flex-col gap-4">
    {#snippet children({ errors, processing })}
        <div>
            <label for="title">标题</label>
            <input id="title" name="title" type="text" required />
            {#if errors.title}
                <p class="error">{errors.title}</p>
            {/if}
        </div>

        <div>
            <label for="content">正文</label>
            <textarea id="content" name="content"></textarea>
            {#if errors.content}
                <p class="error">{errors.content}</p>
            {/if}
        </div>

        <button type="submit" disabled={processing}>
            {processing ? '提交中...' : '发布'}
        </button>
    {/snippet}
</Form>
```

`{#snippet children({ errors, processing })}` 是 Svelte 的 snippet 语法,用来向组件传入内容块(相当于 Vue 的 slot 或 React 的 render props)。`errors` 和 `processing` 由 `Form` 组件自动计算并传入。表单字段用 HTML 原生的 `name` 属性,而不是 `bind:value`,让浏览器的标准表单数据采集照常工作。

### 启动套件的模式

启动套件通过 [Wayfinder](/zh/blog/wayfinder-introduction) 把路由当作对象管理。`store.form()` 会返回包含路由对象的 `action` 与 `method` 的对象,直接 spread 到 `<Form>`。

```svelte theme={null}
<script lang="ts">
    import { Form } from '@inertiajs/svelte'
    import { store } from '@/routes/login'
</script>

<Form
    {...store.form()}
    resetOnSuccess={['password']}
    class="flex flex-col gap-6"
>
    {#snippet children({ errors, processing })}
        <!-- 表单内容 -->
    {/snippet}
</Form>
```

`resetOnSuccess` 中列出的字段会在提交成功后自动重置。密码等提交完希望清空的字段可以列在这里。

<Info>
  不用 Wayfinder 时,直接传 `action="/login"` 就能达到同样效果。
</Info>

***

## `useForm` hook

表单处理使用 `@inertiajs/svelte` 的 `useForm` hook,可以简洁实现状态管理、提交、校验错误展示。

### 控制器侧

```php theme={null}
// app/Http/Controllers/PostController.php
class PostController extends Controller
{
    public function store(Request $request)
    {
        $validated = $request->validate([
            'title'   => ['required', 'string', 'max:255'],
            'content' => ['required', 'string'],
        ]);

        Post::create($validated + ['user_id' => auth()->id()]);

        return redirect()->route('posts.index')
            ->with('success', '文章已创建。');
    }
}
```

### Svelte 表单组件

```svelte theme={null}
<!-- resources/js/pages/posts/Create.svelte -->
<script lang="ts">
    import { useForm } from '@inertiajs/svelte'

    const form = useForm({
        title: '',
        content: '',
    })

    function submit(e: SubmitEvent) {
        e.preventDefault()
        form.post('/posts')
    }
</script>

<form onsubmit={submit}>
    <div>
        <label>标题</label>
        <input type="text" bind:value={form.data.title} />
        {#if form.errors.title}
            <p class="error">{form.errors.title}</p>
        {/if}
    </div>

    <div>
        <label>正文</label>
        <textarea bind:value={form.data.content}></textarea>
        {#if form.errors.content}
            <p class="error">{form.errors.content}</p>
        {/if}
    </div>

    <button type="submit" disabled={form.processing}>
        {form.processing ? '提交中...' : '发布'}
    </button>
</form>
```

`useForm` 返回对象的主要属性:

| 属性 / 方法            | 说明                  |
| ------------------ | ------------------- |
| `form.data`        | 表单数据对象              |
| `form.errors`      | 校验错误(按字段名访问)        |
| `form.processing`  | 提交中为 `true`(用来禁用按钮) |
| `form.isDirty`     | 相对初始值发生变化时为 `true`  |
| `form.post(url)`   | 以 POST 提交           |
| `form.put(url)`    | 以 PUT 提交(更新)        |
| `form.delete(url)` | 以 DELETE 提交         |
| `form.reset()`     | 表单重置为初始值            |

校验返回错误时,`useForm` 会保留已输入内容并显示错误。配合 `bind:value`,可以实现顺畅的表单体验。

***

## 共享数据(Shared Data)

所有页面都需要的公共数据(登录中的用户信息、闪存消息等)可以在 `HandleInertiaRequests` 中间件的 `share()` 方法中定义。

```php theme={null}
// app/Http/Middleware/HandleInertiaRequests.php
use Illuminate\Http\Request;
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'auth' => [
                'user' => $request->user()
                    ? $request->user()->only('id', 'name', 'email')
                    : null,
            ],
            'flash' => [
                'success' => $request->session()->get('success'),
                'error'   => $request->session()->get('error'),
            ],
        ]);
    }
}
```

在 Svelte 组件里通过 `usePage()` 访问共享数据。

```svelte theme={null}
<script lang="ts">
    import { usePage } from '@inertiajs/svelte'

    type SharedProps = {
        auth: {
            user: { id: number; name: string; email: string } | null
        }
        flash: {
            success: string | null
            error: string | null
        }
    }

    const page = usePage<SharedProps>()
</script>

<header>
    {#if page.props.auth.user}
        <span>{page.props.auth.user.name}</span>
    {:else}
        <span>游客</span>
    {/if}
</header>

{#if page.props.flash.success}
    <div class="alert-success">{page.props.flash.success}</div>
{/if}
```

<Info>
  共享数据会包含在每次请求中,建议只保留必要的最少数据。用 `fn()` 惰性求值时,只有真正访问时才求值。
</Info>

***

## Svelte 5 的响应式(runes)

Inertia × Svelte 开发中你需要了解的 Svelte 5 runes。

### `$state` —— 响应式状态

```svelte theme={null}
<script lang="ts">
    let count = $state(0)
    let isOpen = $state(false)
    let items = $state<string[]>([])
</script>

<p>{count}</p>
<button onclick={() => count++}>+1</button>
<button onclick={() => isOpen = !isOpen}>切换</button>
```

用 `$state` 声明的变量会自动变成响应式,值变化时 DOM 会自动更新。相当于 Vue 的 `ref` 或 React 的 `useState`,但不用访问 `.value`,直接赋值就能更新状态。

### `$derived` —— 派生值

```svelte theme={null}
<script lang="ts">
    let posts = $state<{ title: string; published: boolean }[]>([])

    // 只保留 published 为 true 的文章
    let publishedPosts = $derived(posts.filter(post => post.published))

    // 当有多个依赖时
    let summary = $derived.by(() => {
        const total = posts.length
        const published = publishedPosts.length
        return `共 ${total} 篇中已发布 ${published} 篇`
    })
</script>

<p>{summary}</p>
```

`$derived` 会在依赖值变化时自动重新计算。相当于 Vue 的 `computed` 或 React 的 `useMemo`。

### `$effect` —— 副作用处理

```svelte theme={null}
<script lang="ts">
    let query = $state('')

    // query 变化时执行
    $effect(() => {
        console.log('查询变了:', query)

        // 可以返回清理函数
        return () => {
            console.log('清理')
        }
    })
</script>

<input bind:value={query} placeholder="搜索..." />
```

`$effect` 会在其依赖的 `$state` 变化时执行。相当于 React 的 `useEffect`,但不需要显式写依赖数组,使用到的 `$state` 会被自动追踪。

***

## shadcn-svelte 组件

启动套件里已经集成了 [shadcn-svelte](https://www.shadcn-svelte.com/)。它与 React 的 shadcn/ui 设计思路相同,是一个可以把代码拷贝到项目里自由定制的组件库。

### 添加组件

```shell theme={null}
npx shadcn-svelte@latest add button
npx shadcn-svelte@latest add input
npx shadcn-svelte@latest add card
```

命令执行后,组件源码会被放到 `resources/js/components/ui/`。

### 用法

```svelte theme={null}
<script lang="ts">
    import { Button } from '@/components/ui/button'
    import { Input } from '@/components/ui/input'
    import * as Card from '@/components/ui/card'
    import { useForm } from '@inertiajs/svelte'

    const form = useForm({ email: '', password: '' })

    function submit(e: SubmitEvent) {
        e.preventDefault()
        form.post('/login')
    }
</script>

<Card.Root class="w-96">
    <Card.Header>
        <Card.Title>登录</Card.Title>
    </Card.Header>
    <Card.Content>
        <form onsubmit={submit} class="space-y-4">
            <Input
                type="email"
                bind:value={form.data.email}
                placeholder="邮箱"
            />
            {#if form.errors.email}
                <p class="text-sm text-red-500">{form.errors.email}</p>
            {/if}

            <Input
                type="password"
                bind:value={form.data.password}
                placeholder="密码"
            />

            <Button type="submit" disabled={form.processing} class="w-full">
                {form.processing ? '登录中...' : '登录'}
            </Button>
        </form>
    </Card.Content>
</Card.Root>
```

启动套件里预置了 Button、Input、Card、Dialog、Dropdown 等常用组件。想要更多组件时,可以随时用 `npx shadcn-svelte@latest add` 添加。

***

## 总结

Svelte 与 Laravel 组合起来,特别是通过 Inertia 采用“现代单体”结构时,会显现出真正的实力。基于编译器的设计让包体积很小,runes 语法让响应式表达更加明确、易懂。

| 要素                  | 作用                          |
| ------------------- | --------------------------- |
| Laravel 控制器         | 路由、数据获取、校验                  |
| `Inertia::render()` | 从控制器把数据传给 Svelte 组件         |
| Svelte 页面组件         | 用 `$props()` 接收 props 渲染 UI |
| `useForm`           | 表单状态管理、提交、错误展示              |
| `Link` 组件           | 无需整页刷新的页面跳转                 |
| `usePage().props`   | 访问共享数据                      |
| shadcn-svelte       | 标配组件库                       |

用 Inertia × Svelte,你可以拿到 Laravel 后端的简洁与 Svelte 精简写法双重加成的开发体验。用启动套件创建项目,连认证页面在内都是现成的,可以马上开始开发。

<Card title="Inertia.js 官方文档" icon="book-open" href="https://inertiajs.com">
  Inertia v3 的完整功能请参考官方文档。
</Card>


## Related topics

- [React 入门 —— 与 Inertia × Laravel 搭配使用的基础](/zh/blog/react-introduction.md)
- [Vue.js 入门 —— 与 Inertia × Laravel 搭配使用的基础](/zh/blog/vue-introduction.md)
- [前端](/zh/frontend.md)
- [认证入门](/zh/authentication.md)
- [开始学习 Laravel 前需要具备的知识](/zh/true-tutorial.md)
