Vue 2.6 到 2.7 升级中嵌套 Ref 解包问题
问题背景
在 Vue 2.6 中使用 @vue/composition-api@1.1.13
时,开发者可通过 Composition API 管理响应式逻辑。然而,升级到 Vue 2.7 后,由于框架内置了 Composition API 并引入了 嵌套 Ref 自动解包 特性(类似 Vue 3),原有代码可能因响应式结构变化而失效。以下是一个典型问题场景:
原始代码(Vue 2.6 正常,Vue 2.7 异常)
export abstract class Shape {
status?: Ref<STATUS> = ref(STATUS.NONE); // Status 定义为 Ref 类型
constructor(options) {
// 监听 status 的变化(Vue 2.7 中失效)
watch(this.status, (newVal) => {
console.log("Status changed:", newVal);
});
}
}
// 将实例存入 Ref 数组
const areaLines = ref<HotareaVisible<Line>[]>([]);
areaLines.value.push(new Line({ ... }));
// 监听数组变化(可能因嵌套 Ref 解包导致异常)
watch(areaLines, (lines) => { ... });
- Vue 2.6 行为:
status
作为Ref
对象被正确监听,areaLines
中的Shape
实例保持原始结构。 - Vue 2.7 问题: 自动解包导致
status
被转换为原始值,watch(this.status, ...)
失效,且areaLines
的响应式逻辑异常。
问题根源
1. Vue 2.7 的自动解包机制
Vue 2.7 在访问嵌套 Ref 时,会 自动提取最内层值,而非保留 Ref 对象。例如:
class Shape {
status = ref(0);
}
const container = ref([new Shape()]);
// Vue 2.7 输出:status 被解包为 0
console.log(container.value[0].status); // 0(而非 Ref 对象)
2. 代码中的嵌套场景
Shape
实例的status
属性为Ref<STATUS>
。- 当实例被存入
Ref<HotareaVisible<Line>[]>
数组时,Vue 2.7 会递归解包其属性,导致status
失去 Ref 特性。
解决方案
方案 1:使用 shallowRef
包裹容器
通过浅层响应式容器避免嵌套 Ref 被自动解包。
实现步骤
修改容器定义 将
ref([])
替换为shallowRef([])
:import { shallowRef } from 'vue'; const areaLines = shallowRef<HotareaVisible<Line>[]>([]);
保留原有
status
定义Shape
类无需修改:export abstract class Shape { status?: Ref<STATUS> = ref(STATUS.NONE); }
原理与验证
shallowRef
特性:仅追踪.value
的变化,不递归处理内部对象。验证示例:
class Shape { status = ref(0); } const container = shallowRef([new Shape()]); console.log(container.value[0].status); // 输出 Ref 对象(未被解包)
适用场景
- 需保留现有 Ref 结构,且容器仅需浅层响应。
- 适用于大型数组/对象容器,避免深度解包的性能损耗。
方案 2:使用 reactive
替代 Ref
通过响应式对象避免嵌套解包问题,提升状态稳定性。
实现步骤
重构
status
为响应式对象export abstract class Shape { status = reactive({ value: STATUS.NONE }); constructor(options) { // 监听属性值变化 watch(() => this.status.value, (newVal) => { console.log("Status changed:", newVal); }); } }
操作状态时直接修改属性
const shape = new Shape(); shape.status.value = STATUS.UPDATED; // 触发响应式更新
原理与优势
reactive
的稳定性:对象属性不会被自动解包,嵌套在 Ref 中时仍保持结构。- 明确依赖追踪:通过函数式监听 (
() => this.status.value
) 精准追踪变化。
适用场景
- 复杂项目需长期维护,避免隐式解包风险。
- 状态需要与模板或其他组件深度交互。
总结与最佳实践
以上两种方案都能解决类里面 ref 被解包问题,浅层 ref 确实可以能解决深层被解包问题,但由于项目复杂性,不确定在哪一步又被解包了,再加上浅 ref 监听也是个问题,所以还是采用了reactive 代替了 ref,快速且稳定搞定了该问题。
- 理解自动解包机制
- 优先选择
reactive
- 合理使用
shallowRef
- 升级验证策略