vue 2 升级vue3 : ref 和 v-model 命名为同名

0 阅读1分钟

ref 和 v-model 命名为同名

在 Vue 2 中之所以能“侥幸”运行,是因为 Vue 2 的响应式机制(Object.defineProperty)和 $ refs 的存储机制是隔离的。
但在 Vue 3 中,这一“潜规则”被彻底打破。这不仅仅是命名习惯的问题,而是触及了 Vue 3 响应式原理和 组件实例结构的根本性变革

⚠️ “同名”在 Vue 3 中的具体报错表现

当 ref="xxx" 和 v-model="xxx"(或 :model="xxx")同名时,在 Vue 3 中通常会出现以下三种情况:

1. 数据被“劫持”覆盖(最常见)

  • 现象:页面初始化正常,但一旦输入框输入内容,或者组件更新,页面报错 TypeError: xxx is not a function 或 Cannot set property xx of undefined

  • 原因

    • Vue 3 的 setup 返回的对象是响应式的(基于 Proxy)。
    • 当组件挂载时,Vue 试图将模板引用(ref)赋值给这个响应式对象的 xxx 属性。
    • 这会导致原本的普通数据对象(如 { name: '' })被直接替换为组件实例(或 DOM 元素)。
    • 后续 v-model 尝试更新数据时,发现 xxx 已经变成了一个 DOM 对象,导致赋值失败或类型错误。

2. 响应式丢失

  • 现象:输入框可以输入,但视图不更新,或者表单验证不触发。
  • 原因:由于 Proxy 的拦截机制,当你把一个 DOM 元素赋值给原本是数据的变量时,破坏了原有的响应式依赖链条。Vue 无法再追踪到数据的变化。

3. defineExpose 冲突

  • 现象:父组件无法通过 xxxRef.value 调用子组件方法。
  • 原因:如果子组件内部也使用了同名的 v-model 和 ref,子组件的实例暴露逻辑会混乱,导致父组件拿到的 ref 是一个数据对象而不是组件实例。

🚀 Vue 2 -> Vue 3 升级:除了“同名”还有哪些常见坑?

除了你提到的 ref 与 v-model 同名问题,升级过程中以下几点也极易出错:

1. this 指向的变化 (setup 中的坑)

  • Vue 2datamethodscomputed 都挂载在 this 上,随处可用。

  • Vue 3setup 函数中没有 this。如果你在 setup 中定义了变量或函数,必须通过 return 暴露给模板,或者使用 ref/reactive 包装。

    • 坑点:在 setup 中直接写函数,模板里调用会报 is not defined

2. 事件总线 (EventBus) 消失

  • Vue 2:常用 new Vue() 实例作为事件总线。

  • Vue 3:实例不再实现事件接口。 $ on$ off$ emit (父传子除外) 被移除。

    • 解决方案:使用外部的事件触发器(如 mitt 或 tiny-emitter)。

3. filters 被移除

  • Vue 2:常用 filters 格式化文本。

  • Vue 3filters 被彻底移除。

    • 解决方案:改用方法调用计算属性

4. scoped 样式穿透写法变更

  • Vue 2:使用 >>> 或 /deep/ 穿透 scoped 样式。
  • Vue 3:使用 :deep()
/* Vue 3 写法 */ 

2.parent :deep(.child) { 3 color: red; 4}

5. v-if 优先级高于 v-for

  • Vue 2v-for 优先级高于 v-if(虽然不推荐混用)。

  • Vue 3v-if 优先级高于 v-for。如果两者同时存在,v-if 将无法访问 v-for 里的变量。

    • 解决方案:绝对不要在同一个元素上同时使用 v-if 和 v-for,改用计算属性过滤数据。

6. 插槽 (slot) 语法废弃

  • Vue 2:使用 slot 和 slot-scope attribute。

  • Vue 3:废弃旧语法,统一使用 v-slot 指令。

    • 解决方案:全部改为 #slotName 或 v-slot:slotName

7. 全局 API 变更

  • Vue 2Vue.componentVue.directive 是全局挂载。
  • Vue 3:全局 API 变更为 createApp 实例上的方法。

// Vue 3 2const app = createApp({}) 3app.component('my-comp', MyComp)

📌 总结建议

针对 ref 和 v-model 同名的问题,唯一的最佳实践就是“物理隔离”命名

  • 数据流:保持业务语义,如 userFormsearchParams
  • 引用流:强制加后缀,如 userFormRefsearchParamsRef

这样不仅解决了 Vue 3 的底层冲突,也让代码的意图一目了然:带 Ref 的是用来操作 DOM 或调用方法的,不带的是用来存数据的