试一周 Svelte 5:runes 模式 + 编译时优化的"无 vDOM"哲学

起因

React 写多了开始审美疲劳:useState 一个数字、useEffect 一个 fetch、
useMemo 一堆 callback...... 想试试别的范式。Svelte 5 的卖点是
"编译时把响应式翻译成命令式代码"——bundle 极小,写法更接近原生 JS。

跟一个小项目(个人博客 + 评论系统)试了一周。

npm create vite@latest myblog -- --template svelte-ts
cd myblog
npm i
npm run dev

或者 SvelteKit(带 SSR / routing / API routes):

npx sv create myblog

第一个组件

<!-- Counter.svelte -->
<script lang="ts">
  let count = $state(0)
  let doubled = $derived(count * 2)

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

<button onclick={increment}>
  {count} (doubled: {doubled})
</button>

<style>
  button {
    padding: 8px 16px;
    border-radius: 4px;
  }
</style>

$state$derived 是 Svelte 5 引入的 "runes"——显式标记响应式
变量。

跟 React 对比:

const [count, setCount] = useState(0)
const doubled = useMemo(() => count * 2, [count])

return (
  <button onClick={() => setCount(c => c + 1)}>
    {count} (doubled: {doubled})
  </button>
)

Svelte 优势:

  • count++ 直接改,无需 setter
  • doubled 自动追踪依赖,无需 deps 数组
  • <style> scoped 内置
  • 模板更接近 HTML,无 className 之类

bundle 大小:上面 Svelte 组件编译后约 1KB;React 等价组件需要 React +
ReactDOM ~45 KB。

$effect: 副作用

<script>
  let count = $state(0)

  $effect(() => {
    console.log(`count changed to ${count}`)
    document.title = `Count: ${count}`
  })
</script>

$effect 类似 React useEffect 但自动追踪用到的响应式变量,
不需要 deps 数组。count 一变就重跑。

父子通信:$props / $bindable

<!-- Child.svelte -->
<script lang="ts">
  let { name, count = $bindable(0) } = $props<{
    name: string
    count: number
  }>()
</script>

<input bind:value={count} />
<p>{name}: {count}</p>
<!-- Parent.svelte -->
<script>
  let n = $state(0)
</script>

<Child name="counter" bind:count={n} />
<p>Parent sees: {n}</p>

bind: 是 Vue v-model 的等价物。$bindable 让 prop 双向。

列表渲染

<script>
  let items = $state([1, 2, 3])

  function add() {
    items.push(items.length + 1)   // ✅ 直接 push,Svelte 5 能追踪
  }
</script>

{#each items as item, i (item)}
  <div>{i}: {item}</div>
{/each}

<button onclick={add}>add</button>

(item) 是 key(类似 React key)。

{#each} / {#if} / {#await} 是 Svelte 模板语法。

异步:{#await}

<script>
  let promise = fetch('/api/users/1').then(r => r.json())
</script>

{#await promise}
  <p>loading...</p>
{:then user}
  <p>{user.name}</p>
{:catch err}
  <p>error: {err.message}</p>
{/await}

直接在模板里处理 promise,不需要 useState + useEffect。

SvelteKit:File-based routing + SSR

src/routes/
├── +page.svelte           # /
├── about/+page.svelte     # /about
├── posts/
│   ├── +page.svelte       # /posts
│   ├── +page.server.ts    # 数据预取(server-only)
│   └── [id]/+page.svelte  # /posts/:id
// src/routes/posts/+page.server.ts
export async function load() {
  const posts = await db.posts.findMany()
  return { posts }
}
<!-- src/routes/posts/+page.svelte -->
<script>
  let { data } = $props()
</script>

{#each data.posts as post}
  <a href="/posts/{post.id}">{post.title}</a>
{/each}

load 在服务端跑,结果通过 data prop 传给页面。
类似 Next.js getServerSideProps 但更轻量。

API routes

// src/routes/api/posts/+server.ts
import { json } from '@sveltejs/kit'

export async function GET() {
  const posts = await db.posts.findMany()
  return json(posts)
}

export async function POST({ request }) {
  const data = await request.json()
  const post = await db.posts.create({ data })
  return json(post, { status: 201 })
}

文件即 API endpoint。

状态管理

不需要 Redux / Zustand。直接 $state 在共享模块里:

// src/lib/auth.svelte.ts
export const auth = $state({
  user: null as User | null,
})

export async function login(email, pw) {
  const r = await fetch('/api/login', ...)
  auth.user = await r.json()
}

任何组件 import 后修改 auth.user,所有用到的地方自动更新。

bundle 大小对比

同一个 todo app:

bundle (gzipped)
React + Redux 78 KB
React + Zustand 52 KB
Vue 3 41 KB
Svelte 5 11 KB

Svelte 编译模式让框架本身的运行时极小。

性能 benchmark

js-framework-benchmark
显示 Svelte 在多数操作上比 React 快 1.5-3 倍(虽然 React 19 + compiler
之后差距缩小)。

何时选 Svelte

  • 个人项目 / 小团队 / 喜欢简洁
  • bundle size 关键场景(嵌入式 widget / PWA)
  • 已有 web 基础,不想被框架抽象绑死

何时选 React:

  • 大生态需求(组件库 / 招聘 / 大公司支持)
  • 已有 React 团队
  • React Native 跨端

缺点

  1. 生态相对小:组件库 / 工具链 / 教程都比 React 少
  2. 招聘难:Svelte 工程师比 React 少很多
  3. Svelte 5 runes 模式刚出:4 → 5 迁移有小痛
  4. 大型应用案例少:Apple Music 等用了但还不算主流

一周体验感受

  • 写起来明显比 React 顺手(无 useState 仪式、无 deps 数组焦虑)
  • 编译后产物小到惊喜
  • 但生态查个稍复杂的库选择少 / 文档少
  • 对于个人博客 / 小工具站推荐;上生产前评估团队 / 长期维护成本

踩过的坑

  1. Svelte 4 教程 / 文档不适用 Svelte 5export let foolet { foo } = $props()
    $:$derived/$effect。教程要看 5.x。

  2. $state 必须在 script setup 顶层:不能在条件 / 函数里。

  3. 数组 / 对象的响应性items.push(...) Svelte 5 能追踪,
    但深层嵌套 items[0].nested.value = ... 仍要小心。深层用 store。

  4. SSR + 浏览器 APIwindow.localStorage 在 SSR 时报错。
    if (browser) { ... } 包起来或 onMount 里访问。

  5. VSCode 插件:要装官方 Svelte for VSCode + 语言服务器。
    IntelliSense / 跳定义都依赖。

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。