Svelte 卸载后仍留住 derived
untrack 读取多层 derived 后,卸载组件可能仍被长期数据源留住。核对 Svelte 5.57.2 依赖连接修复与堆快照路径。
路由组件已经卸载,堆快照中却仍有一条从长期存活的数据源到 derived、组件上下文,再到分离 DOM 的引用链。如果组件曾在 $effect 或 onMount 中通过 untrack 读取一串 derived,应检查旧 Svelte 的依赖连接问题。
具体反馈来自 Svelte 5.57.0 与 SvelteKit 2.70.2。已核对的正式 Svelte 5.57.2 修正了这条连接条件。不能仅凭内存上涨就断定属于它;图片缓存、应用自己的订阅和其他仍存活的引用,也需要分别排除。
没有追踪读取者,子 derived 却连上了源
untrack 让指定读取不成为当前响应式计算的依赖。它仍会取得值;需要重新计算的 derived 也仍可能在读取时求值。
旧实现的问题出现在多层 derived 中:父 derived 从不追踪的读取进入计算,自己没有连接到子 derived;内部条件却因为仍处在 effect 更新阶段,让子 derived 连接到更上游的依赖。
通常,最后一个读取者退出后会断开依赖链。这次子 derived 自己没有读取者,却注册在上游,清理便缺少对应的断开时机。若上游是跨页面存在的数据源,它可能继续持有 derived 的组件上下文和已经离开文档的节点。
排查要看引用链与读取方式,而不是把所有 derived 都删除。留意模板原先也读取同一串 derived,模板分支随后离开,再由一个不追踪的读取重新求值的顺序。onMount 的回调也不追踪其内部读取,不能只搜索显式写出的 untrack。
下面是需要留意的组件结构片段。data、count、ticks、show 都由已有应用定义,片段只展示读取关系,不是可独立运行的泄漏测试:
<script>
import { untrack } from 'svelte';
const snapshot = $derived({ n: data.n, c: count });
$effect(() => {
void ticks;
const current = untrack(() => snapshot);
// 按应用需要消费 current。
});
</script>
{#if show}{snapshot.c}{/if}
修正连接条件,保留 untrack 的用途
5.57.2 只有在读取者确实处于连接状态时,才把断开的 derived 接回依赖链。不会因为“正在更新 effect”就单独允许子 derived 连接。正常追踪的读取仍能重新连接并更新结果。
使用包含此修复、与项目兼容的正式版本,并确认锁文件解析结果。不要把应用改为持续追踪所有值,仅为了让清理看似有对应读取者;这会改变 effect 的触发次数与业务语义。手工操作 Svelte 的内部反应列表,也容易破坏其他组件的依赖。
应用自己注册的事件、订阅、计时器仍需按生命周期清理。运行时修复不会自动释放外部库保留的回调。也不要用强制 GC 或定期刷新页面代替查明引用链。
在获准的测试环境中重复进入、离开目标页面,等待实际卸载与异步工作结束,再比较堆快照中的保留路径。修复后,已离开的组件不应再由这条无读取者的 derived 链留住;正常的追踪更新与 untrack 读取仍应返回正确结果。其他缓存或真实存活组件的内存可以继续存在,不能要求整个堆立即归零。


