Skip to main content

生命周期钩子

在 Svelte 5 中,组件生命周期仅由两部分组成:创建和销毁。中间的一切,比如当某些状态更新时,与整个组件无关,只有需要对状态变化做出响应的部分才会得到通知。

这是因为在底层,变化的最小单位实际上并不是组件,而是组件在初始化时设置的(渲染)effects。因此,不存在”更新前”/"更新后”这样的钩子。

onMount

onMount 函数传入一个回调函数,在组件挂载到 DOM 后立即运行。它必须在组件初始化期间被调用(但不需要位于组件内部;可以从外部模块调用)。

onMount 不会在服务端渲染的组件中运行。

<script>
	import { onMount } from 'svelte';

	onMount(() => {
		console.log('组件已挂载');
	});
</script>

如果 onMount 返回一个函数,它将在组件卸载时被调用。

<script>
	import { onMount } from 'svelte';

	onMount(() => {
		const interval = setInterval(() => {
			console.log('beep');
		}, 1000);

		return () => clearInterval(interval);
	});
</script>

这种行为只在传递给 onMount 的函数同步返回值时才有效。async 函数总是返回一个 Promise,因此不能同步返回一个函数。

onDestroy

安排一个回调在组件卸载前立即运行。

```dts function onDestroy(fn: () => any): void; ```

onMountbeforeUpdateafterUpdateonDestroy 中,这是唯一一个在服务端组件内运行的。

<script>
	import { onDestroy } from 'svelte';

	onDestroy(() => {
		console.log('组件正在被销毁');
	});
</script>

tick

虽然没有”更新后”钩子,但你可以使用 tick 来确保在继续之前 UI 已更新。tick 返回一个 promise,该 promise 在所有待处理的状态更改被应用后 resolve,如果没有待处理的状态更改,则在下一个微任务中 resolve。

<script>
	import { tick } from 'svelte';

	$effect.pre(() => {
		console.log('组件即将更新');
		tick().then(() => {
				console.log('组件刚刚更新');
		});
	});
</script>

已废弃:beforeUpdate / afterUpdate

Svelte 4 有两个在组件整体更新前后运行的钩子。为了向后兼容,这些钩子在 Svelte 5 中被模拟实现,但在使用符文的组件中不可用。

<script>
	import { beforeUpdate, afterUpdate } from 'svelte';

	beforeUpdate(() => {
		console.log('组件即将更新');
	});

	afterUpdate(() => {
		console.log('组件刚刚更新');
	});
</script>

不要使用 beforeUpdate,应该使用 $effect.pre;不要使用 afterUpdate,应该使用 $effect — 这些符文提供更细粒度的控制,只对你实际关心的更改做出响应。

聊天窗口示例

我们来实现一个聊天窗口,当出现新消息时自动滚动到底部(前提是你已经滚动到底部了),我们需要在更新 DOM 之前先测量它。

在 Svelte 4 中,我们使用 beforeUpdate 来实现,但这是一个有缺陷的方法 — 它在每次更新前都会触发,无论是否相关。在下面的示例中,我们需要引入像 updatingMessages 这样的检查,以确保在有人切换暗黑模式时不会影响滚动位置。

使用符文,我们可以使用 $effect.pre,它的行为与 $effect 相同,但在 DOM 更新之前运行。只要我们在 effect 内明确引用 messages,它就会在 messages 改变时运行,而在 theme 改变时不会运行。

因此,beforeUpdate 以及同样麻烦的小伙伴 afterUpdate 在 Svelte 5 中已被废弃。

<script>
	import { beforeUpdate, afterUpdate, tick } from 'svelte';

	let updatingMessages = false;
	let theme = $state('dark');
	let messages = $state([]);

	let viewport;

	beforeUpdate(() => {
	$effect.pre(() => {
		if (!updatingMessages) return;
		messages;
		const autoscroll = viewport && viewport.offsetHeight + viewport.scrollTop > viewport.scrollHeight - 50;

		if (autoscroll) {
			tick().then(() => {
				viewport.scrollTo(0, viewport.scrollHeight);
			});
		}

		updatingMessages = false;
	});

	function handleKeydown(event) {
		if (event.key === 'Enter') {
			const text = event.target.value;
			if (!text) return;

			updatingMessages = true;
			messages = [...messages, text];
			event.target.value = '';
		}
	}

	function toggle() {
		toggleValue = !toggleValue;
	}
</script>

<div class:dark={theme === 'dark'}>
	<div bind:this={viewport}>
		{#each messages as message}
			<p>{message}</p>
		{/each}
	</div>

	<input onkeydown={handleKeydown} />

	<button onclick={toggle}> 切换暗黑模式 </button>
</div>

在 GitHub 编辑此页面