生命周期钩子
在 Svelte 5 中,组件的生命周期只包含两个部分:它的创建和它的销毁。介于两者之间的——当某些状态被更新时——并不与组件整体相关;只有那些需要对状态变化做出反应的部分才会被通知。这是因为在底层,变化的最小单位实际上并不是组件,而是组件在初始化时建立的(渲染)effects。因此,并不存在所谓的"更新前"/"更新后"钩子。
onMount
onMount 函数会安排一个回调,在组件被挂载到 DOM 后立即运行。它必须在组件初始化期间被调用(但不需要_位于_组件内部;它可以从外部模块调用)。
onMount 不会在服务器端渲染的组件中运行。
<script>
import { onMount } from 'svelte';
onMount(() => {
console.log('the component has mounted');
});
</script>
如果从 onMount 返回一个函数,它会在组件被卸载时被调用。
<script>
import { onMount } from 'svelte';
onMount(() => {
const interval = setInterval(() => {
console.log('beep');
}, 1000);
return () => clearInterval(interval);
});
</script>
[!NOTE] 这一行为只有当传入
onMount的函数是_同步_的时才会生效。async函数总是返回一个Promise。
onDestroy
安排一个回调,在组件被卸载之前立即运行。
在 onMount、beforeUpdate、afterUpdate 和 onDestroy 之中,这是唯一会在服务端组件中运行的钩子。
<script>
import { onDestroy } from 'svelte';
onDestroy(() => {
console.log('the component is being destroyed');
});
</script>
tick
虽然没有"更新后"钩子,但你可以使用 tick 来确保 UI 在继续之前已经更新。tick 返回一个 promise,一旦任何待处理的状态变更被应用,或者在下一个微任务(如果没有变更时)它就会 resolve。
<script>
import { tick } from 'svelte';
$effect.pre(() => {
console.log('the component is about to update');
tick().then(() => {
console.log('the component just updated');
});
});
</script>
已弃用:beforeUpdate / afterUpdate
Svelte 4 包含了在组件整体更新之前和之后运行的钩子。为了向后兼容,这些钩子在 Svelte 5 中被做了填充(shim),但在使用符文的组件中不可用。
<script>
import { beforeUpdate, afterUpdate } from 'svelte';
beforeUpdate(() => {
console.log('the component is about to update');
});
afterUpdate(() => {
console.log('the component just updated');
});
</script>
请改用 $effect.pre 来替代 beforeUpdate,用 $effect 来替代 afterUpdate——这些符文提供了更细粒度的控制,并且只对你真正感兴趣的变化做出反应。
聊天窗口示例
要实现一个在新消息出现时自动滚动到底部(但仅当你_已经_滚动到底部时)的聊天窗口,我们需要在更新 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() {
theme = theme === 'dark' ? 'light' : 'dark';
}
</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}> Toggle dark mode </button>
</div>