Vue2中v-model做了哪些额外处理?两种绑定写法为何不等价
问题对应示例代码
<template> <div> value: {{ obj.c }} <my-input v-model="obj.c"/> <my-input :value="obj.c" @change="(val) => (obj.c = val)"/> </div> </template> <script> export default { data(){ return { obj: {} } } } </script>
其中my-input为自定义组件,实际运行时v-model绑定写法可以正常更新视图,但手动通过:value传值+@change事件赋值的写法无法触发视图更新。
结论
这两种写法并不完全等价,表现差异来自Vue2响应式系统的固有特性,以及v-model在编译阶段做的额外封装处理。
手动写法失效的根本原因
Vue2的响应式实现基于Object.defineProperty,只有初始化阶段就已经在data中声明的属性,才会被getter/setter劫持,成功收集视图依赖、触发更新。
示例代码中,data里返回的obj初始是空对象,没有提前声明c属性,手动实现的@change="(val) => (obj.c = val)"是裸赋值操作,本质是给响应式对象obj新增了一个从来没被劫持过的属性c,这个操作在Vue2中默认无法触发视图更新——新增的属性没有对应的setter通知watcher更新,自然页面不会重新渲染。
v-model除基础语法糖外的额外处理
大部分开发者对v-model的认知停留在「语法糖,默认等价于传递value prop + 监听对应事件赋值」,但实际上Vue2在编译v-model时做了两层关键的额外处理:
- 自动适配自定义组件的
model配置:自定义组件可以通过model选项修改v-model绑定的prop名和触发事件名,v-model编译时会自动读取这个配置做绑定,不需要手动调整prop和事件名,这也是示例中v-model能正确匹配my-input组件抛出的change事件的原因。 - 赋值逻辑自动做响应式兜底:v-model编译生成的赋值逻辑不是简单的
绑定值 = 新值的裸赋值,内部会先判断目标属性是否已经是响应式属性:如果是给对象新增不存在的属性,会自动调用内部的$set(即Vue.set)逻辑给新属性做响应式劫持,保证赋值后能正常触发视图更新;只有属性本身已经是响应式的时候,才会走直接赋值的逻辑。
这就是为什么直接给未声明的obj.c赋值不更新,但是用v-model绑定就正常——v-model自动帮你做了$set(obj, 'c', val)的操作。
修复方案
如果要让手动写法和v-model表现完全一致,只需要把赋值逻辑改成用$set实现即可:
<my-input :value="obj.c" @change="(val) => $set(obj, 'c', val)"/>
改完之后两种写法的表现就完全一致了。
补充说明:这个差异在Vue3中已经不存在,Vue3基于Proxy实现响应式,可以天然拦截对象新增属性的操作,不需要$set做额外处理,两种写法在Vue3环境下表现完全等价。
内容的提问来源于stack exchange,提问作者Oscar
相关产品推荐
相关产品推荐

