生命周期钩子
本教程共 50 篇 · 第 34 篇 · 更新于 2026-08-05 · 约 7 分钟阅读
本节目标:学会使用 onMount、onDestroy 和 tick 等生命周期函数,理解 Svelte 5 中生命周期与 $effect 的关系,了解 beforeUpdate/afterUpdate 为何被废弃。
Svelte 5 的生命周期简化了
在 Svelte 5 中,组件的生命周期其实只有两个阶段:创建和销毁。中间的更新过程不再属于”组件级别”——而是属于具体的 effect。
打个比方,以前组件像一栋大楼,大楼有”建造""翻新""拆除”三个阶段。Svelte 5 把”翻新”细化到了每间房间——哪间房间需要翻新就翻新哪间,不用整栋楼一起动工。
这意味着大部分你以前用生命周期钩子做的事情,现在用 $effect 就能更精准地完成。
onMount 挂载后
onMount 在组件挂载到 DOM 后执行。它是最常用的生命周期钩子。
<script>
import { onMount } from 'svelte';
onMount(() => {
console.log('组件已挂载');
});
</script>
清理函数
如果 onMount 的回调返回一个函数,它会在组件卸载时执行。
<script>
import { onMount } from 'svelte';
onMount(() => {
const interval = setInterval(() => {
console.log('嘀嗒');
}, 1000);
// 组件卸载时清理定时器
return () => clearInterval(interval);
});
</script>
Note清理函数只在回调是同步函数时有效。
async函数返回的是 Promise,Svelte 无法从中提取清理函数。如果你需要异步操作加清理,用$effect代替。
onMount 不在服务端运行
onMount 只在浏览器中执行,不会在服务端渲染时运行。如果你有只该在客户端执行的代码,放在 onMount 里是安全的。
实战:获取数据
<script>
import { onMount } from 'svelte';
let users = $state([]);
onMount(async () => {
const res = await fetch('/api/users');
users = await res.json();
});
</script>
{#if users.length === 0}
<p>加载中...</p>
{:else}
<ul>
{#each users as user}
<li>{user.name}</li>
{/each}
</ul>
{/if}
Tip在 Svelte 5 中,你也可以用
$effect来替代onMount——$effect会在组件挂载后首次执行。但如果你只需要执行一次且不需要响应依赖变化,onMount语义更清晰。
onDestroy 销毁前
onDestroy 在组件即将从 DOM 中移除时执行。它适合做清理工作。
<script>
import { onDestroy } from 'svelte';
const interval = setInterval(() => {
console.log('运行中');
}, 1000);
// 组件销毁前清理
onDestroy(() => clearInterval(interval));
</script>
Note
onDestroy是四个生命周期钩子中唯一在服务端渲染时也会执行的。其他钩子(onMount、beforeUpdate、afterUpdate)在 SSR 中不运行。
onMount vs onDestroy 做清理
两种写法效果一样,选你觉得顺眼的:
<script>
import { onMount, onDestroy } from 'svelte';
let interval;
// 方式一:onMount 返回清理函数
onMount(() => {
interval = setInterval(() => console.log('嘀'), 1000);
return () => clearInterval(interval);
});
// 方式二:单独用 onDestroy
// interval = setInterval(() => console.log('嘀'), 1000);
// onDestroy(() => clearInterval(interval));
</script>
tick 等待 DOM 更新
Svelte 的状态更新不是同步反映到 DOM 上的——它会批量处理,在下一个微任务中统一更新。有时候你需要在 DOM 更新后做某些操作,这时用 tick()。
tick() 返回一个 Promise,在待处理的状态变更都应用到 DOM 后 resolve。
<script>
import { tick } from 'svelte';
let text = $state('初始值');
let paragraph;
async function update() {
text = '新值';
// 此时 DOM 还没更新
console.log(paragraph.textContent); // 输出 "初始值"
// 等待 DOM 更新
await tick();
// 现在 DOM 更新了
console.log(paragraph.textContent); // 输出 "新值"
}
</script>
<p bind:this={paragraph}>{text}</p>
<button onclick={update}>更新</button>
实战:聊天窗口自动滚动
tick() 最经典的场景是聊天窗口——新消息到达后滚动到底部。
<script>
import { tick } from 'svelte';
let messages = $state([]);
let viewport;
async function sendMessage(text) {
messages = [...messages, text];
// 等 DOM 更新后再滚动
await tick();
viewport.scrollTo(0, viewport.scrollHeight);
}
function handleKeydown(event) {
if (event.key === 'Enter' && event.target.value.trim()) {
sendMessage(event.target.value);
event.target.value = '';
}
}
</script>
<div bind:this={viewport} style="height: 200px; overflow-y: auto; border: 1px solid #ccc;">
{#each messages as message}
<p>{message}</p>
{/each}
</div>
<input onkeydown={handleKeydown} placeholder="输入消息后回车" />
$effect.pre:更新前的钩子
$effect.pre 类似于 $effect,但在 DOM 更新之前运行。它是 Svelte 5 中替代 beforeUpdate 的方案。
<script>
import { tick } from 'svelte';
let messages = $state([]);
let viewport;
$effect.pre(() => {
// 读取 messages 让这个 effect 依赖它
messages;
// DOM 更新前检查是否需要滚动
const shouldScroll = viewport &&
viewport.offsetHeight + viewport.scrollTop > viewport.scrollHeight - 50;
if (shouldScroll) {
tick().then(() => {
viewport.scrollTo(0, viewport.scrollHeight);
});
}
});
</script>
Note
$effect.pre只在它引用的响应式值变化时执行,而不是每次组件更新都执行。这比 Svelte 4 的beforeUpdate精准得多。
废弃的 beforeUpdate / afterUpdate
Svelte 4 有 beforeUpdate 和 afterUpdate 两个钩子。它们在每次组件更新前后执行。
Svelte 5 中这两个钩子被废弃了,原因很简单:它们在每次更新时都执行,不管更新是否跟你有关。这导致了很多不必要的执行和难以追踪的 bug。
<script>
import { beforeUpdate, afterUpdate } from 'svelte';
// 废弃写法(不推荐)
beforeUpdate(() => {
console.log('即将更新');
});
afterUpdate(() => {
console.log('刚刚更新');
});
</script>
替代方案
| 废弃钩子 | Svelte 5 替代 |
|---|---|
beforeUpdate | $effect.pre |
afterUpdate | $effect |
<script>
// Svelte 5 推荐写法
// 替代 beforeUpdate
$effect.pre(() => {
someState; // 读取状态建立依赖
console.log('someState 即将更新');
});
// 替代 afterUpdate
$effect(() => {
someState; // 读取状态建立依赖
console.log('someState 刚刚更新');
});
</script>
Note
beforeUpdate和afterUpdate在 Runes 模式下不可用。如果你在 Runes 组件中调用它们会报错。用$effect.pre和$effect替代。
生命周期与 $effect 的关系
Svelte 5 的核心思想是:用 $effect 替代大部分生命周期钩子。
| 传统钩子 | $effect 替代 | 说明 |
|---|---|---|
onMount | $effect(首次执行) | $effect 在挂载后首次执行 |
onDestroy | $effect 的清理函数 | $effect 返回的函数在重新运行或卸载时执行 |
beforeUpdate | $effect.pre | 在 DOM 更新前执行 |
afterUpdate | $effect | 在 DOM 更新后执行 |
<script>
let count = $state(0);
// 一个 $effect 同时处理"挂载"和"卸载"
$effect(() => {
console.log('count 变了或组件刚挂载:', count);
return () => {
console.log('count 即将变化或组件即将卸载');
};
});
</script>
Tip虽然
$effect可以替代大部分钩子,但onMount仍然有存在价值——它语义明确,表示”只执行一次”的意图。$effect会在依赖变化时重新执行,如果你不想要这个行为,用onMount更合适。
从外部模块调用
生命周期函数不一定要在组件内部调用——它们可以在外部模块中调用,只要调用时机在组件初始化阶段。
// utils.js
import { onMount } from 'svelte';
export function setupInterval(callback, delay) {
onMount(() => {
const interval = setInterval(callback, delay);
return () => clearInterval(interval);
});
}
<script>
import { setupInterval } from './utils.js';
let time = $state('');
setupInterval(() => {
time = new Date().toLocaleTimeString();
}, 1000);
</script>
<p>当前时间:{time}</p>
本节回顾
- Svelte 5 的组件生命周期简化为创建和销毁两个阶段
onMount在组件挂载后执行,返回的函数在卸载时清理,SSR 时不执行onDestroy在组件卸载前执行,是唯一在 SSR 中也运行的钩子tick()返回 Promise,在 DOM 更新完成后 resolve,适合”更新后立即操作 DOM”的场景beforeUpdate和afterUpdate已废弃,用$effect.pre和$effect替代$effect可以替代大部分生命周期钩子,但onMount在表达”只执行一次”时语义更清晰- 生命周期函数可以从外部模块调用,只要在组件初始化阶段触发