Skip to content

综合实战示例

本章把前面学到的 Runes 与组件知识串起来,通过几个可交互的完整示例巩固理解。每个卡片都是真实运行的 Svelte 5.57 组件。

示例一:计数器($state + 事件)

最经典的入门示例。展示 $state 声明状态、onclick 处理事件。

计数器 · $stateSvelte 5 · Live

涉及的知识点

  • $state(0) 声明响应式计数
  • onclick={fn} 绑定点击处理
  • 函数内直接 count += 1 修改状态
svelte
<script>
  let count = $state(0);
  function increment() { count += 1; }
</script>

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

示例二:几何计算器($derived 派生)

拖动滑块改变长宽,面积、周长、形状判断全部由 $derived 实时计算。

派生状态 · $derivedSvelte 5 · Live

涉及的知识点

  • bind:value 双向绑定滑块到状态
  • $derived(expr) 计算派生值
  • 派生值依赖多个状态,任一变化都会重算
  • style:width / style:height 动态内联样式
  • 三元表达式结合 class: 指令
svelte
<script>
  let width = $state(10);
  let height = $state(5);
  const area = $derived(width * height);
  const perimeter = $derived(2 * (width + height));
  const isSquare = $derived(width === height);
</script>

<input type="range" min="1" max="20" bind:value={width} />
<div style:width="{width * 12}px" style:height="{height * 12}px"></div>

打开控制台

这个例子没有用 $effect,因为「计算值」应该用 $derived 而非副作用 —— 这正是我们强调的正确用法。

示例三:主题切换($props + $bindable

一个用 $props 声明属性、用 $bindable 支持双向绑定的开关组件。

主题切换 · $props / $bindableSvelte 5 · Live

涉及的知识点

  • let { theme = $bindable('light'), label = '主题' } = $props()
  • 带默认值的普通 prop(label
  • 可双向绑定的 prop(theme
svelte
<script>
  let { theme = $bindable('light'), label = '主题' } = $props();
  function toggle() {
    theme = theme === 'light' ? 'dark' : 'light';
  }
</script>

<button onclick={toggle}>{theme === 'light' ? '🌞' : '🌙'} {label}</button>

示例四:音量控制(父子双向绑定)

父组件持有 volume 状态,通过 bind:volume 与子组件双向同步。拖动滑块或点静音,两边同时更新。

双向绑定 · $bindableSvelte 5 · Live

涉及的知识点

  • 父组件 let volume = $state(50)
  • <VolumeControl bind:volume /> 建立双向绑定
  • 子组件 $bindable prop 内部也能改
  • 父组件用 $derived 判断是否静音
svelte
<!-- 父组件 -->
<script>
  import VolumeControl from './VolumeControl.svelte';
  let volume = $state(50);
  let muted = $derived(volume === 0);
</script>

<VolumeControl bind:volume />
<button onclick={() => (volume = muted ? 50 : 0)}>
  {muted ? '取消静音' : '静音'}
</button>

示例五:卡片内容分发(Snippets)

{#snippet} 定义具名片段、{@render} 渲染,实现灵活的组件组合。footer 还能读到父组件的实时状态。

Snippets · {@render}Svelte 5 · Live

涉及的知识点

  • {@render children()} 渲染默认内容
  • {#snippet header()} / {#snippet footer()} 具名片段
  • {#if header} 条件渲染可选片段
  • 片段内可访问父组件状态(likes
svelte
<!-- 子组件 Card.svelte -->
<script>
  let { children, header, footer } = $props();
</script>

<div class="card">
  {#if header}<div class="card__header">{@render header()}</div>{/if}
  <div class="card__body">{@render children()}</div>
  {#if footer}<div class="card__footer">{@render footer()}</div>{/if}
</div>

示例六:组件事件(回调即属性)

子组件通过回调 props「派发事件」,父组件记录日志。这是 Svelte 5 取代 createEventDispatcher 的核心模式。

组件事件 · 回调即属性Svelte 5 · Live

涉及的知识点

  • 子组件 let { onnotify, onreset } = $props()
  • 调用回调 = 派发事件
  • 父组件传入处理函数
  • 父组件用数组 $state 维护日志
svelte
<!-- 子组件 -->
<script>
  let { onnotify = () => {} } = $props();
  let clicks = $state(0);
  function handleClick() {
    onnotify(`被点击了 ${++clicks} 次`);
  }
</script>

<button onclick={handleClick}>点我触发事件</button>

示例七:完整待办应用(综合运用)

一个功能完整的待办清单,几乎用到了所有核心特性。强烈建议打开浏览器控制台,观察 $effect 打印的日志。

待办清单 · 综合实战Svelte 5 · Live

涉及的知识点(综合)

  • $state 管理数组、字符串、筛选条件等多个状态
  • $derived 计算剩余数量、过滤可见项
  • $effect 把状态同步到控制台(副作用)
  • 数组方法 push / filter 直接修改(深度响应式)
  • {#each ... (todo.id)} 带 key 的列表渲染 + {:else} 空状态
  • bind:value / bind:checked 表单双向绑定
  • onsubmit + e.preventDefault() 表单提交
  • class:done={...} / class:active={...} 条件类名
svelte
<script>
  let todos = $state([/* ... */]);
  let draft = $state('');
  let filter = $state('all');
  let nextId = 4;

  const remaining = $derived(todos.filter((t) => !t.done).length);
  const visible = $derived(
    filter === 'active' ? todos.filter((t) => !t.done)
      : filter === 'done' ? todos.filter((t) => t.done)
      : todos
  );

  $effect(() => {
    console.log(`[Todo demo] 剩余 ${remaining} 项,共 ${todos.length} 项`);
  });

  function addTodo() {
    const text = draft.trim();
    if (!text) return;
    todos.push({ id: nextId++, text, done: false });
    draft = '';
  }
</script>

<form onsubmit={(e) => { e.preventDefault(); addTodo(); }}>
  <input bind:value={draft} placeholder="添加任务…" />
  <button type="submit">添加</button>
</form>

{#each visible as todo (todo.id)}
  <label>
    <input type="checkbox" bind:checked={todo.done} />
    <span class:done={todo.done}>{todo.text}</span>
  </label>
  <button onclick={() => (todos = todos.filter((t) => t.id !== todo.id))}>×</button>
{:else}
  <p>没有匹配的任务 🎉</p>
{/each}

把这些搬进你的项目

本站所有示例组件都是纯 Svelte 5,可以直接复制到你的项目里:

  1. .svelte 文件放进 src/lib/
  2. 在需要的地方 import 并使用
  3. mount() 或在 SvelteKit 页面中直接引用

源码位置

本教程的示例组件源码位于项目 docs/demos/ 目录,你可以对照学习每个 .svelte 文件的完整实现。

下一步

基于 VitePress 构建 · 示例使用 Svelte 5.57