首页 / Svelte 5 入门教程 / 生命周期钩子

Svelte 5 入门教程

生命周期钩子

本教程共 50 篇 · 第 34 篇 · 更新于 2026-08-05 · 约 7 分钟阅读

SvelteSvelte 5生命周期onMountonDestroytick$effect

本节目标:学会使用 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 是四个生命周期钩子中唯一在服务端渲染时也会执行的。其他钩子(onMountbeforeUpdateafterUpdate)在 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 有 beforeUpdateafterUpdate 两个钩子。它们在每次组件更新前后执行。

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

beforeUpdateafterUpdate 在 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”的场景
  • beforeUpdateafterUpdate 已废弃,用 $effect.pre$effect 替代
  • $effect 可以替代大部分生命周期钩子,但 onMount 在表达”只执行一次”时语义更清晰
  • 生命周期函数可以从外部模块调用,只要在组件初始化阶段触发