什么是 Svelte
Svelte 在 JavaScript 框架中占据了非常独特的一席之地。React、Vue 是运行时库 ,而 Svelte 是编译器 。它在构建时把组件编译为纯 JavaScript,因此浏览器无需接收额外的框架代码。
Svelte 最鲜明的特点是不使用虚拟 DOM 。状态发生变化时,Svelte 在编译时生成的代码会直接更新 DOM。因此可以做到非常轻量、高速的 UI。
本文讲解的是 Svelte 5 与 Inertia v3 的组合。Laravel 13 的启动套件默认使用这一组合。
Svelte 5 的 runes
Svelte 5(2024 年发布)引入了名为 Runes 的新响应式系统。使用 $state、$derived、$effect 等特殊函数(runes)来声明响应式状态。相比 Svelte 4 之前的隐式响应式,它更加显式、更易预测。
< script lang = "ts" >
let count = $ state ( 0 )
function increment () {
count ++
}
</ script >
< button onclick = { increment } > { count } </ button >
Laravel 的 Svelte 启动套件默认使用 Svelte 5 + TypeScript。本文示例也都用 TypeScript(lang="ts")。
在 Laravel 中的定位
相较 Vue 和 React,Svelte 与 Laravel 的关系非常新,被官方启动套件采纳 才是它的第一次官方支持。
2026 年的 Laravel 13 在启动套件中正式加入 Svelte,让 Svelte 成为 Laravel 生态中的官方前端选项之一。它和 Vue、React 平起平坐,可以在 laravel new 的交互式提示中选择。
对不少 Laravel 用户来说 Svelte 还不算熟悉,但它以编译带来的轻量包体积 和简洁语法 著称,从 Vue 或 React 迁移过来,你可能会惊讶“居然要写这么少”。
当前主流:Inertia × Svelte
当前 Laravel 中使用 Svelte 的主流方式就是 Inertia × Svelte 。Inertia 让你不必设计 API,就能直接从 Laravel 控制器把数据传给 Svelte 组件,实现“现代单体”架构。
通过启动套件(推荐)
新项目最省事的方式就是使用启动套件。
在交互式提示中选择 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 实现)
手动安装
在已有项目中引入时,分别安装服务端和客户端。
# 服务端(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 插件。
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 插件会负责页面的自动解析和挂载,入口写得非常精简。
import { createInertiaApp } from '@inertiajs/svelte'
createInertiaApp ()
目录结构
启动套件把 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> 三块组成。
< script lang = "ts" >
// 逻辑(TypeScript)
let name = $ state ( 'Laravel' )
</ script >
<!-- 模板(类 HTML 语法) -->
< h1 > Hello, { name } ! </ h1 >
< style >
/* 作用域 CSS(只对该组件生效) */
h1 {
color : #ff2d20 ;
}
</ style >
结构与 Vue 的 SFC 相似,但模板和脚本的书写量更少。<style> 默认作用域受限,不用担心类名冲突。
模板语法
变量展开与表达式
在模板中用 {} 嵌入 JavaScript 的值或表达式。
< script lang = "ts" >
let name = $ state ( '世界' )
let count = $ state ( 3 )
</ script >
< p > 你好, { name } ! </ p >
< p > 2 倍是 { count * 2 } </ p >
{#if} —— 条件分支
< 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} —— 列表渲染
< 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 可以让表单元素的值和响应式变量双向同步。
< 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 的形式接收。
控制器
// 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。
<!-- 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 完成,避免整页重载。
< 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。
@inertiajs/svelte 提供的 <Form> 组件是启动套件认证页面推荐的表单提交样式。通过 props 指定 action 和 method,内部用 {#snippet} 写内容。
基本用法
< 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 把路由当作对象管理。store.form() 会返回包含路由对象的 action 与 method 的对象,直接 spread 到 <Form>。
< 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 中列出的字段会在提交成功后自动重置。密码等提交完希望清空的字段可以列在这里。
不用 Wayfinder 时,直接传 action="/login" 就能达到同样效果。
表单处理使用 @inertiajs/svelte 的 useForm hook,可以简洁实现状态管理、提交、校验错误展示。
控制器侧
// 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 表单组件
<!-- 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() 方法中定义。
// 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() 访问共享数据。
< 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 }
共享数据会包含在每次请求中,建议只保留必要的最少数据。用 fn() 惰性求值时,只有真正访问时才求值。
Svelte 5 的响应式(runes)
Inertia × Svelte 开发中你需要了解的 Svelte 5 runes。
$state —— 响应式状态
< 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 —— 派生值
< 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 —— 副作用处理
< 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 。它与 React 的 shadcn/ui 设计思路相同,是一个可以把代码拷贝到项目里自由定制的组件库。
添加组件
npx shadcn-svelte@latest add button
npx shadcn-svelte@latest add input
npx shadcn-svelte@latest add card
命令执行后,组件源码会被放到 resources/js/components/ui/。
< 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 精简写法双重加成的开发体验。用启动套件创建项目,连认证页面在内都是现成的,可以马上开始开发。
Inertia.js 官方文档 Inertia v3 的完整功能请参考官方文档。