DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

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 读取仍应返回正确结果。其他缓存或真实存活组件的内存可以继续存在,不能要求整个堆立即归零。