Svelte 清理函数读 derived 后,新分支不再更新
Svelte 5.57.2 中,销毁阶段读取待重算的 derived,可能干扰条件分支依赖。将资源清理绑定到创建时的句柄,避免清理时重新读取活跃派生状态。
切换选中项时,派生值第一次显示正确,随后修改新选中项的数据却不再更新。若 attachment 或 effect 的清理函数会读取同一个 $derived,应把清理阶段的读取一并纳入排查。
Svelte 5.57.2 有这条具体表现:条件 derived 依赖选中分支,切换时旧清理函数读取了待重算的 derived,后续新分支的变化没有正常传递。它与简单的赋值失败或普通对象没有响应式代理不是同一个问题。
清理时的读取也可能触发重算
例如 derived 根据当前 key 读取 a 或 b 的字段。key 从 a 切到 b 后,模板应转向追踪 b;但在销毁旧 attachment 的过程中读取该值,会让计算处于一轮更新尚未完成的阶段。
派生值不是永远只读取已经算好的普通快照。需要更新时,读取本身可以进入求值和依赖处理。正式运行时对销毁阶段已有特殊分支,但“不要把值标记为干净”并不自动证明条件依赖的所有变化都安全。
诊断时保留 key 切换、旧清理执行、第一次新分支显示与后续字段修改的顺序。临时移除清理中的 derived 读取后恢复,只是定位线索;还要核对应用是否有重复订阅、捕获了旧对象或意外替换状态。
资源清理使用创建时拿到的句柄
清理如果只是释放本次创建的资源,通常不需要在结束时重新读取当前派生状态。应保存创建资源时返回的句柄,例如在已有 effect 中采用这种结构:
$effect(() => {
const handle = connectToSelection(selectedId);
return () => handle.dispose();
});
这里的 connectToSelection 是应用自己的资源接口,dispose() 也要使用真实库所提供的方法。清理与本次创建的句柄配对,避免在旧资源退出时又根据新选中项去决定释放谁。不要把资源释放动作移出清理函数,否则会留下连接或监听器。
这项调整的前提是清理契约只关心本次资源。若业务确实需要销毁瞬间的最终派生值,例如提交最后状态,简单改用创建时快照会改变含义,应先将最终状态采集与资源销毁拆出明确流程,再验证时序,不能机械替换。
不要手动修运行时依赖标志
给 $derived 加额外无意义读取、操作内部反应列表,或把所有计算都塞进 untrack,可能改变订阅关系,不能视作这条问题的可靠通用修复。一个值被打印正确,也不证明它仍订阅了后续变化。
验收应先切换到 b,再多次修改 b 的字段,随后切回 a 重复检查,同时确认每个旧资源只释放一次、离开页面后没有残留订阅。不要只看切换后的第一次渲染;这条故障恰好可能在第一次看似正确以后才暴露。


