DEBUG.WIKI
晚安云;香港 CN2 ¥15/月
PanstarCloud:部署应用,接入 AI,云服务器与 AI 网关
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →

Vue 把浅层 Map 赋给 ref 后变深:检查 isShallow 误判

shallowReactive Map 的 isShallow 返回 false,可能让 ref setter 去掉浅层代理再创建深层代理。需要保留集合身份时可评估 shallowRef。

shallowReactive(new Map()) 明明用了浅层 API,isShallow() 却返回 false;把它赋给普通 ref 后,集合里的普通对象还可能变成深层响应式对象。若使用 Vue 3.5.43,检查集合代理是否暴露了浅层标志。

已核对的 3.5.43 集合 get trap 处理了 reactive、readonly 和 raw 标志,却没有处理 shallow 标志。普通对象的 shallowReactive 与 shallowReadonly 不走同一个 trap,所以对象对照正常并不能证明 Map、Set 也正确。

判断函数的误报会影响后续赋值

普通 ref 的 setter 会检查新值是否 shallow。误判为 false 后,它可能取回 raw 集合,再按深层 reactive 包装,原本希望保留的浅层集合代理就丢失了。这不仅是调试工具显示错一个标签。

先比较新建浅层集合、赋值前后的引用身份,以及取出成员后是否仍为普通对象。若实际问题是深层对象中的 ref 已被解包,属于另一种读取规则,应按Map 值中的 ref 解包检查,不要把所有 .value 问题归于浅层标志。

外层只需要跟踪替换时,可用 shallowRef

当外层容器的职责是保存已经管理好的浅层集合,而不替它加深层代理时,可以采用:

import { isReactive, shallowReactive, shallowRef } from 'vue';

const shallowMap = shallowReactive(new Map([
  ['item', { count: 0 }],
]));
const holder = shallowRef();
holder.value = shallowMap;

console.log(holder.value === shallowMap); // true
console.log(isReactive(holder.value.get('item'))); // false

这里 outer ref 只追踪 .value 的替换,集合自己的 shallowReactive 仍负责集合操作。holder.value.set(...) 会按集合代理自身的规则工作;取出的普通对象直接改 count,不应被当成深层自动追踪。

这项处理适合项目原本就需要浅层语义的地方。已有页面依赖内部对象深层变化时,换成 shallowRef 会改变更新行为,需重新安排数据流。它也没有修好 isShallow(shallowMap) 的返回值,不能继续把这个受影响的判断函数当成业务分支的唯一依据。

不要在对象上手工写 __v_isShallow 等内部标志。那会依赖框架内部实现,并可能影响其他代理路径。更不要把全部状态改成 markRaw,只为了让一个判断返回预期结果。

分别检查集合和成员

沿原来“已有 ref 后续接收浅层 Map”的赋值入口验证,不能只测构造时一次传值。确认引用仍是原浅层集合,Map 的增删会驱动依赖它的计算,而普通成员对象不会被意外深度代理;Set 与 shallowReadonly 也应按实际用途分别检查。

已核对的 3.5.43 正式代码仍缺该集合标志分支,不把关闭状态视为所有已装版本都修复。后续采用正式修复,再用 isShallow、普通 ref 赋值和集合操作一起回归。保留旧锁文件和持有方式,回退时避免浅层与深层容器混用导致部分页面只偶然更新。