组件生命周期 #

一、生命周期概述 #

Svelte 组件从创建到销毁经历不同的阶段,每个阶段都有对应的生命周期函数。

text
┌─────────────────────────────────────────────────────────────┐
│                    Svelte 组件生命周期                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  创建阶段                                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  onMount ──→ 组件挂载到DOM后执行                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                         ↓                                   │
│  更新阶段                                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  beforeUpdate ──→ DOM更新前执行                       │   │
│  │  afterUpdate  ──→ DOM更新后执行                       │   │
│  └─────────────────────────────────────────────────────┘   │
│                         ↓                                   │
│  销毁阶段                                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  onDestroy ──→ 组件销毁前执行                         │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、onMount #

2.1 基本用法 #

svelte
<script>
  import { onMount } from 'svelte';
  
  onMount(() => {
    console.log('Component mounted');
    
    return () => {
      console.log('Cleanup on unmount');
    };
  });
</script>

<p>Component content</p>

2.2 DOM 操作 #

svelte
<script>
  import { onMount } from 'svelte';
  
  let canvas;
  let ctx;
  
  onMount(() => {
    ctx = canvas.getContext('2d');
    ctx.fillStyle = 'red';
    ctx.fillRect(10, 10, 50, 50);
  });
</script>

<canvas bind:this={canvas} width="200" height="200"></canvas>

2.3 数据获取 #

svelte
<script>
  import { onMount } from 'svelte';
  
  let data = [];
  let loading = true;
  let error = null;
  
  onMount(async () => {
    try {
      const response = await fetch('/api/data');
      data = await response.json();
    } catch (e) {
      error = e.message;
    } finally {
      loading = false;
    }
  });
</script>

{#if loading}
  <p>Loading...</p>
{:else if error}
  <p>Error: {error}</p>
{:else}
  <ul>
    {#each data as item}
      <li>{item.name}</li>
    {/each}
  </ul>
{/if}

2.4 清理函数 #

svelte
<script>
  import { onMount } from 'svelte';
  
  let count = 0;
  let interval;
  
  onMount(() => {
    interval = setInterval(() => {
      count += 1;
    }, 1000);
    
    return () => {
      clearInterval(interval);
      console.log('Interval cleared');
    };
  });
</script>

<p>Count: {count}</p>

2.5 Svelte 5 $effect #

svelte
<script>
  let count = $state(0);
  
  $effect(() => {
    const interval = setInterval(() => {
      count += 1;
    }, 1000);
    
    return () => {
      clearInterval(interval);
    };
  });
</script>

<p>Count: {count}</p>

三、onDestroy #

3.1 基本用法 #

svelte
<script>
  import { onDestroy } from 'svelte';
  
  let timer = setInterval(() => {
    console.log('tick');
  }, 1000);
  
  onDestroy(() => {
    clearInterval(timer);
    console.log('Component destroyed');
  });
</script>

3.2 清理订阅 #

svelte
<script>
  import { onDestroy } from 'svelte';
  
  let socket;
  
  function connect() {
    socket = new WebSocket('wss://example.com');
    
    socket.onopen = () => {
      console.log('Connected');
    };
    
    socket.onmessage = (event) => {
      console.log('Message:', event.data);
    };
  }
  
  connect();
  
  onDestroy(() => {
    if (socket) {
      socket.close();
      console.log('WebSocket closed');
    }
  });
</script>

3.3 清理事件监听 #

svelte
<script>
  import { onDestroy } from 'svelte';
  
  function handleResize() {
    console.log('Window resized');
  }
  
  window.addEventListener('resize', handleResize);
  
  onDestroy(() => {
    window.removeEventListener('resize', handleResize);
  });
</script>

四、beforeUpdate 和 afterUpdate #

4.1 beforeUpdate #

svelte
<script>
  import { beforeUpdate } from 'svelte';
  
  let count = 0;
  
  beforeUpdate(() => {
    console.log('Before update, count:', count);
  });
</script>

<button onclick={() => count += 1}>
  Count: {count}
</button>

4.2 afterUpdate #

svelte
<script>
  import { afterUpdate } from 'svelte';
  
  let messages = [];
  let chatContainer;
  
  afterUpdate(() => {
    if (chatContainer) {
      chatContainer.scrollTop = chatContainer.scrollHeight;
    }
  });
  
  function addMessage(text) {
    messages = [...messages, { text, time: new Date() }];
  }
</script>

<div class="chat" bind:this={chatContainer}>
  {#each messages as message}
    <p>{message.text}</p>
  {/each}
</div>

<button onclick={() => addMessage('New message')}>
  Add Message
</button>

4.3 组合使用 #

svelte
<script>
  import { beforeUpdate, afterUpdate } from 'svelte';
  
  let count = 0;
  let previousCount = 0;
  
  beforeUpdate(() => {
    previousCount = count;
  });
  
  afterUpdate(() => {
    console.log(`Count changed from ${previousCount} to ${count}`);
  });
</script>

<button onclick={() => count += 1}>
  Count: {count}
</button>

五、tick #

5.1 基本用法 #

tick 函数返回一个 Promise,在下一个 DOM 更新周期后 resolve。

svelte
<script>
  import { tick } from 'svelte';
  
  let text = '';
  let input;
  
  async function handleClick() {
    text = 'Updated';
    await tick();
    input.focus();
    input.select();
  }
</script>

<input bind:this={input} bind:value={text} />
<button onclick={handleClick}>Update and Focus</button>

5.2 等待 DOM 更新 #

svelte
<script>
  import { tick } from 'svelte';
  
  let items = [];
  let listRef;
  
  async function addItem() {
    items = [...items, `Item ${items.length + 1}`];
    await tick();
    listRef.lastElementChild?.scrollIntoView({ behavior: 'smooth' });
  }
</script>

<ul bind:this={listRef}>
  {#each items as item}
    <li>{item}</li>
  {/each}
</ul>

<button onclick={addItem}>Add Item</button>

5.3 批量更新 #

svelte
<script>
  import { tick } from 'svelte';
  
  let a = 0;
  let b = 0;
  let c = 0;
  
  async function updateAll() {
    a = 1;
    b = 2;
    c = 3;
    
    await tick();
    console.log('All updates applied');
  }
</script>

<p>a={a}, b={b}, c={c}</p>
<button onclick={updateAll}>Update All</button>

六、Svelte 5 生命周期 #

6.1 $effect 替代生命周期 #

svelte
<script>
  let count = $state(0);
  
  $effect(() => {
    console.log('Component mounted or count changed:', count);
    
    return () => {
      console.log('Cleanup');
    };
  });
</script>

<button onclick={() => count++}>
  Count: {count}
</button>

6.2 仅挂载时执行 #

svelte
<script>
  let mounted = false;
  
  $effect(() => {
    if (!mounted) {
      mounted = true;
      console.log('Mounted once');
    }
  });
</script>

6.3 $effect.pre #

svelte
<script>
  let items = $state([]);
  
  $effect.pre(() => {
    console.log('Before DOM update, items:', items.length);
  });
</script>

七、实际应用示例 #

7.1 计时器组件 #

svelte
<script>
  import { onMount, onDestroy } from 'svelte';
  
  let { 
    duration = 60,
    onTimeUp,
    onPause,
    onResume
  } = $props();
  
  let timeLeft = $state(duration);
  let isRunning = $state(false);
  let interval;
  
  onMount(() => {
    start();
  });
  
  onDestroy(() => {
    stop();
  });
  
  function start() {
    if (isRunning) return;
    isRunning = true;
    interval = setInterval(() => {
      timeLeft -= 1;
      if (timeLeft <= 0) {
        stop();
        onTimeUp?.();
      }
    }, 1000);
    onResume?.();
  }
  
  function stop() {
    if (!isRunning) return;
    isRunning = false;
    clearInterval(interval);
    onPause?.();
  }
  
  function reset() {
    stop();
    timeLeft = duration;
  }
  
  let minutes = $derived(Math.floor(timeLeft / 60));
  let seconds = $derived(timeLeft % 60);
</script>

<div class="timer">
  <span class="time">
    {String(minutes).padStart(2, '0')}:{String(seconds).padStart(2, '0')}
  </span>
  
  <div class="controls">
    {#if isRunning}
      <button onclick={stop}>Pause</button>
    {:else}
      <button onclick={start}>Resume</button>
    {/if}
    <button onclick={reset}>Reset</button>
  </div>
</div>

<style>
  .timer {
    text-align: center;
    padding: 2rem;
  }
  
  .time {
    font-size: 3rem;
    font-family: monospace;
  }
  
  .controls {
    margin-top: 1rem;
    display: flex;
    gap: 0.5rem;
    justify-content: center;
  }
</style>

7.2 WebSocket 连接组件 #

svelte
<script>
  import { onMount, onDestroy } from 'svelte';
  
  let { 
    url,
    onMessage,
    onOpen,
    onClose,
    onError
  } = $props();
  
  let socket = null;
  let connected = $state(false);
  let messages = $state([]);
  
  onMount(() => {
    connect();
  });
  
  onDestroy(() => {
    disconnect();
  });
  
  function connect() {
    socket = new WebSocket(url);
    
    socket.onopen = () => {
      connected = true;
      onOpen?.();
    };
    
    socket.onmessage = (event) => {
      const data = JSON.parse(event.data);
      messages = [...messages, data];
      onMessage?.(data);
    };
    
    socket.onclose = () => {
      connected = false;
      onClose?.();
    };
    
    socket.onerror = (error) => {
      onError?.(error);
    };
  }
  
  function disconnect() {
    if (socket) {
      socket.close();
      socket = null;
    }
  }
  
  function send(data) {
    if (socket && connected) {
      socket.send(JSON.stringify(data));
    }
  }
</script>

<div class="websocket-status" class:connected class:disconnected={!connected}>
  {connected ? '🟢 Connected' : '🔴 Disconnected'}
</div>

<slot {send} {messages} {connected} />

7.3 本地存储同步 #

svelte
<script>
  import { onMount } from 'svelte';
  
  let { 
    key,
    defaultValue = null
  } = $props();
  
  let value = $state(defaultValue);
  let loaded = $state(false);
  
  onMount(() => {
    const stored = localStorage.getItem(key);
    if (stored !== null) {
      try {
        value = JSON.parse(stored);
      } catch {
        value = stored;
      }
    }
    loaded = true;
  });
  
  $effect(() => {
    if (loaded) {
      localStorage.setItem(key, JSON.stringify(value));
    }
  });
</script>

{#if loaded}
  <slot bind:value />
{/if}

八、生命周期对比 #

8.1 Svelte 4 vs Svelte 5 #

Svelte 4 Svelte 5
onMount $effect(首次执行时)
onDestroy $effect 返回的清理函数
beforeUpdate $effect.pre
afterUpdate $effect(每次状态变化后)
tick tick(保持不变)

8.2 迁移示例 #

Svelte 4:

svelte
<script>
  import { onMount, onDestroy } from 'svelte';
  
  let count = 0;
  let interval;
  
  onMount(() => {
    interval = setInterval(() => count++, 1000);
  });
  
  onDestroy(() => {
    clearInterval(interval);
  });
</script>

Svelte 5:

svelte
<script>
  let count = $state(0);
  
  $effect(() => {
    const interval = setInterval(() => count++, 1000);
    return () => clearInterval(interval);
  });
</script>

九、最佳实践 #

9.1 清理资源 #

svelte
<script>
  import { onMount } from 'svelte';
  
  onMount(() => {
    const controller = new AbortController();
    
    fetch('/api/data', { signal: controller.signal })
      .then(r => r.json())
      .then(data => {
        console.log(data);
      });
    
    return () => {
      controller.abort();
    };
  });
</script>

9.2 避免内存泄漏 #

svelte
<script>
  import { onMount, onDestroy } from 'svelte';
  
  let subscriptions = [];
  
  onMount(() => {
    subscriptions.push(
      eventBus.subscribe('event1', handler1),
      eventBus.subscribe('event2', handler2)
    );
  });
  
  onDestroy(() => {
    subscriptions.forEach(unsub => unsub());
  });
</script>

十、总结 #

函数 说明 Svelte 5 替代
onMount 挂载后执行 $effect
onDestroy 销毁前执行 $effect 返回函数
beforeUpdate 更新前执行 $effect.pre
afterUpdate 更新后执行 $effect
tick 等待DOM更新 tick

生命周期要点:

  • onMount 用于初始化和 DOM 操作
  • 返回清理函数在组件销毁时执行
  • Svelte 5 使用 $effect 统一处理
  • 使用 tick 等待 DOM 更新
  • 及时清理定时器、订阅等资源
最后更新:2026-03-28