Svelte 5.57.2 派生值漏掉新增属性:检查 derived 是否在 effect 外创建
模板内 hasOwn 已能更新,事件中创建的类派生值仍卡住,可能是缺失键没有响应式 source。读取属性后再做存在性判断可保留语义。
点击按钮给 $state 新增字段后,直接读取字段的派生值更新了,'x' in state 或 Object.hasOwn(state, 'x') 却仍显示 false。若派生值来自事件处理器里新建的类,检查它创建时是否没有父 effect。
这条条件在 Svelte 5.57.2 中仍存在:缺失属性第一次只经过 has 查询,而 derived 创建于 effect 之外。模板或已有 effect 先读取过同一个键时,可能已经建立响应式 source,因此相似页面又看不出问题。
5.57.1 的 hasOwn 修复没有覆盖所有创建位置
此前自有属性描述查询缺少追踪的路径,已经在 5.57.1 修正。但 5.57.2 的 has trap 为不存在的键新建 source 时,仍受 active_effect !== null 条件限制。无父 effect 的 derived 执行时不满足它,便没有可跟踪的键。
直接 get 读取有另一条创建路径,因此 state.x 仍可能正常。这不表示赋值失败,也不表示 Object.hasOwn 与 in 在业务语义上可以随意互换。
需要存在性语义时,先明确读取同一个键
可在受影响的派生计算中先读取属性,再保留原存在性检查。下面是类字段的局部示例,适用于支持 runes 的 Svelte 编译文件:
class Store {
state = $state({});
hasX = $derived.by(() => {
void this.state.x;
return Object.hasOwn(this.state, 'x');
});
}
void 丢弃读取结果,但这次 get 会给缺失键建立追踪依据;最终布尔值仍由 Object.hasOwn 决定。类需沿原来的事件或模块创建位置使用,不能把它挪到模板里后就宣称原条件已恢复。
这项临时处理面向普通状态数据。属性若是 getter、对象带有其他自定义代理或读取本身有副作用,额外 get 会改变行为,应先核对。动态键也必须在同一次计算中读取和检查相同名称。
不能直接换成 state.x !== undefined:拥有 x 且值为 undefined,与根本没有 x 是不同状态。in 还包含原型链,也不能代替要求自有字段的判断。
新增、undefined 与删除都要走过
在原来无父 effect 的创建入口验证:初始没有 x 时为 false;新增 x: true 后为 true;保留 x 但写 undefined 时仍为 true;删除 x 后回到 false。再覆盖同键的直接读取,确认没有为了让页面更新而改掉存在性规则。
若后续采用正式框架修复,应保留这组创建位置和状态变化做回归,再决定是否移除额外读取。已核对的 5.57.2 正式代理源码仍含父 effect 条件,不能因为较早的 hasOwn 修复已发布就忽略此分支。回退时保存原版本和局部计算,普通快照或非代理对象仍不会自动获得响应式能力。


