响应式声明 #

一、响应式概述 #

Svelte 的响应式系统是其核心特性之一。当状态发生变化时,Svelte 会自动检测并更新相关的 DOM。

1.1 响应式原理 #

text
┌─────────────────────────────────────────────────────────────┐
│                    Svelte 响应式原理                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   编译时                                                    │
│   ┌─────────────────────────────────────────────────────┐  │
│   │  分析代码,建立依赖关系图                            │  │
│   │  状态 A ──→ 派生 B ──→ DOM 更新                     │  │
│   │     └──────→ 派生 C ──→ DOM 更新                    │  │
│   └─────────────────────────────────────────────────────┘  │
│                         ↓                                   │
│   运行时                                                    │
│   ┌─────────────────────────────────────────────────────┐  │
│   │  状态变化时,精确更新依赖项                          │  │
│   │  无需虚拟DOM,直接操作真实DOM                        │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、Svelte 4 响应式语法 #

2.1 基本状态 #

svelte
<script>
  let count = 0;
  
  function increment() {
    count += 1;
  }
</script>

<button on:click={increment}>
  Count: {count}
</button>

2.2 响应式声明 ($:) #

svelte
<script>
  let count = 0;
  
  $: doubled = count * 2;
  $: squared = count * count;
</script>

<p>Count: {count}</p>
<p>Doubled: {doubled}</p>
<p>Squared: {squared}</p>

2.3 响应式语句块 #

svelte
<script>
  let count = 0;
  
  $: {
    console.log('count changed to:', count);
    document.title = `Count: ${count}`;
  }
</script>

2.4 响应式条件 #

svelte
<script>
  let count = 0;
  
  $: if (count > 10) {
    console.log('count is greater than 10');
  }
</script>

三、Svelte 5 Runes #

3.1 $state - 状态声明 #

svelte
<script>
  let count = $state(0);
  let name = $state('Svelte');
  let items = $state([1, 2, 3]);
  let user = $state({ name: 'Alice', age: 25 });
</script>

3.2 $state 的深度响应 #

svelte
<script>
  let user = $state({
    name: 'Alice',
    address: {
      city: 'Beijing',
      street: 'Main St'
    }
  });
  
  function updateCity() {
    user.address.city = 'Shanghai';
  }
</script>

<p>{user.name} lives in {user.address.city}</p>
<button onclick={updateCity}>Move to Shanghai</button>

3.3 $state.raw - 浅响应 #

svelte
<script>
  let items = $state.raw([1, 2, 3]);
  
  function addItem() {
    items = [...items, items.length + 1];
  }
</script>

3.4 类中的 $state #

svelte
<script>
  class Counter {
    count = $state(0);
    
    increment() {
      this.count += 1;
    }
    
    get doubled() {
      return this.count * 2;
    }
  }
  
  let counter = new Counter();
</script>

<p>Count: {counter.count}</p>
<p>Doubled: {counter.doubled}</p>
<button onclick={() => counter.increment()}>+</button>

四、$derived - 派生状态 #

4.1 基本用法 #

svelte
<script>
  let count = $state(0);
  
  let doubled = $derived(count * 2);
  let squared = $derived(count * count);
  let message = $derived(`Count is ${count}`);
</script>

<p>{message}</p>
<p>Doubled: {doubled}</p>
<p>Squared: {squared}</p>

4.2 复杂派生 #

svelte
<script>
  let firstName = $state('John');
  let lastName = $state('Doe');
  
  let fullName = $derived(`${firstName} ${lastName}`);
  
  let items = $state([1, 2, 3, 4, 5]);
  let sum = $derived(items.reduce((a, b) => a + b, 0));
  let average = $derived(sum / items.length);
</script>

<p>Name: {fullName}</p>
<p>Sum: {sum}, Average: {average}</p>

4.3 $derived.by - 复杂计算 #

svelte
<script>
  let items = $state([
    { id: 1, name: 'Apple', price: 1.5 },
    { id: 2, name: 'Banana', price: 2.0 },
    { id: 3, name: 'Orange', price: 1.8 }
  ]);
  
  let statistics = $derived.by(() => {
    const total = items.reduce((sum, item) => sum + item.price, 0);
    const count = items.length;
    const average = total / count;
    const max = Math.max(...items.map(i => i.price));
    const min = Math.min(...items.map(i => i.price));
    
    return { total, count, average, max, min };
  });
</script>

<p>Total: ${statistics.total.toFixed(2)}</p>
<p>Average: ${statistics.average.toFixed(2)}</p>
<p>Range: ${statistics.min} - ${statistics.max}</p>

五、响应式数组操作 #

5.1 数组更新 #

svelte
<script>
  let items = $state([1, 2, 3]);
  
  function addItem() {
    items = [...items, items.length + 1];
  }
  
  function removeItem(index) {
    items = items.filter((_, i) => i !== index);
  }
  
  function updateItem(index, value) {
    items = items.map((item, i) => i === index ? value : item);
  }
</script>

<ul>
  {#each items as item, index}
    <li>
      {item}
      <button onclick={() => removeItem(index)}>删除</button>
    </li>
  {/each}
</ul>
<button onclick={addItem}>添加</button>

5.2 数组方法快捷方式 #

svelte
<script>
  let items = $state([1, 2, 3, 4, 5]);
  
  let filtered = $derived(items.filter(i => i > 2));
  let mapped = $derived(items.map(i => i * 2));
  let sorted = $derived([...items].sort((a, b) => b - a));
</script>

<p>Original: {items.join(', ')}</p>
<p>Filtered: {filtered.join(', ')}</p>
<p>Mapped: {mapped.join(', ')}</p>
<p>Sorted: {sorted.join(', ')}</p>

六、响应式对象操作 #

6.1 对象更新 #

svelte
<script>
  let user = $state({
    name: 'Alice',
    age: 25,
    address: {
      city: 'Beijing'
    }
  });
  
  function updateName(name) {
    user.name = name;
  }
  
  function updateAge(age) {
    user.age = age;
  }
  
  function updateCity(city) {
    user.address.city = city;
  }
</script>

<p>Name: {user.name}</p>
<p>Age: {user.age}</p>
<p>City: {user.address.city}</p>

6.2 对象展开更新 #

svelte
<script>
  let settings = $state({
    theme: 'light',
    fontSize: 14,
    notifications: true
  });
  
  function updateSettings(newSettings) {
    settings = { ...settings, ...newSettings };
  }
  
  function toggleTheme() {
    settings.theme = settings.theme === 'light' ? 'dark' : 'light';
  }
</script>

<button onclick={toggleTheme}>
  Current theme: {settings.theme}
</button>

七、响应式最佳实践 #

7.1 避免过度派生 #

svelte
<script>
  let items = $state([1, 2, 3, 4, 5]);
  
  let sum = $derived(items.reduce((a, b) => a + b, 0));
  
  let average = $derived(sum / items.length);
</script>

7.2 使用 $derived.by 处理复杂逻辑 #

svelte
<script>
  let data = $state([]);
  let filter = $state('');
  let sortKey = $state('name');
  
  let processed = $derived.by(() => {
    let result = data.filter(item => 
      item.name.includes(filter)
    );
    
    result.sort((a, b) => {
      if (a[sortKey] < b[sortKey]) return -1;
      if (a[sortKey] > b[sortKey]) return 1;
      return 0;
    });
    
    return result;
  });
</script>

7.3 合理拆分状态 #

svelte
<script>
  let user = $state({ name: '', email: '' });
  let preferences = $state({ theme: 'light', lang: 'en' });
  let cart = $state([]);
  
  let cartTotal = $derived(
    cart.reduce((sum, item) => sum + item.price * item.quantity, 0)
  );
</script>

八、Svelte 4 vs Svelte 5 对比 #

8.1 语法对比 #

功能 Svelte 4 Svelte 5
状态 let count = 0 let count = $state(0)
派生 $: doubled = count * 2 let doubled = $derived(count * 2)
副作用 $: { ... } $effect(() => { ... })
Props export let prop let { prop } = $props()

8.2 迁移示例 #

Svelte 4:

svelte
<script>
  export let initialCount = 0;
  
  let count = initialCount;
  $: doubled = count * 2;
  $: if (count > 10) {
    console.log('count exceeded 10');
  }
</script>

Svelte 5:

svelte
<script>
  let { initialCount = 0 } = $props();
  
  let count = $state(initialCount);
  let doubled = $derived(count * 2);
  
  $effect(() => {
    if (count > 10) {
      console.log('count exceeded 10');
    }
  });
</script>

九、完整示例 #

9.1 购物车组件 #

svelte
<script>
  let { items = [] } = $props();
  
  let cart = $state([]);
  
  let totalItems = $derived(
    cart.reduce((sum, item) => sum + item.quantity, 0)
  );
  
  let totalPrice = $derived(
    cart.reduce((sum, item) => sum + item.price * item.quantity, 0)
  );
  
  let discount = $derived(totalPrice > 100 ? totalPrice * 0.1 : 0);
  
  let finalPrice = $derived(totalPrice - discount);
  
  function addToCart(item) {
    const existing = cart.find(i => i.id === item.id);
    if (existing) {
      existing.quantity += 1;
    } else {
      cart = [...cart, { ...item, quantity: 1 }];
    }
  }
  
  function removeFromCart(id) {
    cart = cart.filter(item => item.id !== id);
  }
  
  function updateQuantity(id, quantity) {
    cart = cart.map(item => 
      item.id === id ? { ...item, quantity } : item
    );
  }
</script>

<div class="cart">
  <h2>购物车 ({totalItems} 件商品)</h2>
  
  {#if cart.length === 0}
    <p class="empty">购物车是空的</p>
  {:else}
    <ul class="cart-items">
      {#each cart as item}
        <li>
          <span class="name">{item.name}</span>
          <span class="price">${item.price}</span>
          <input 
            type="number" 
            min="1" 
            bind:value={item.quantity}
          />
          <button onclick={() => removeFromCart(item.id)}>删除</button>
        </li>
      {/each}
    </ul>
    
    <div class="summary">
      <p>小计: ${totalPrice.toFixed(2)}</p>
      {#if discount > 0}
        <p class="discount">折扣: -${discount.toFixed(2)}</p>
      {/if}
      <p class="total">总计: ${finalPrice.toFixed(2)}</p>
    </div>
  {/if}
</div>

<style>
  .cart {
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 1rem;
  }
  
  .empty {
    color: #999;
    text-align: center;
    padding: 2rem;
  }
  
  .cart-items li {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.5rem 0;
    border-bottom: 1px solid #eee;
  }
  
  .summary {
    margin-top: 1rem;
    text-align: right;
  }
  
  .discount {
    color: green;
  }
  
  .total {
    font-size: 1.2rem;
    font-weight: bold;
  }
</style>

十、总结 #

概念 说明
$state 声明响应式状态
$derived 声明派生状态
$derived.by 复杂派生计算
$state.raw 浅响应状态

响应式原则:

  • 状态变化自动触发更新
  • 派生状态自动追踪依赖
  • 避免在派生中产生副作用
  • 合理拆分状态,提高可维护性
最后更新:2026-03-28