vue 2 中 v-model 在自定义组件中通过 value prop 和 input 事件实现双向绑定:父组件 v-model="foo" 等价于 :value="foo" @input="val => foo = val",子组件需声明 value prop 并在变更时 $emit('input', newvalue),否则绑定静默失效。

Vue 2 中 v-model 在自定义组件里怎么工作
它不是魔法,而是约定:子组件必须接收 value prop,并在数据变更时触发 input 事件。父组件写 v-model="foo",等价于 :value="foo" @input="val => foo = val"。一旦子组件没按这个契约实现,v-model 就会静默失效——不报错,但绑定断开。
常见错误现象:
- 输入框内容变了,父组件的变量没更新(子组件漏发
input) - 父组件修改了变量,子组件视图不刷新(子组件没监听
value变化或没同步到内部data) - 用
v-model绑定contenteditable元素时报错"v-model directives aren't supported on elements"(因为v-model默认只支持input/textarea/select)
contenteditable + v-html 场景下如何手动实现双向绑定
这类场景无法直接用 v-model,必须拆解为“监听输入”+“响应 prop 更新”两步。核心是避免直接操作 props,而用内部 data 做中转。
实操建议:
- 子组件声明
props: { value: String },同时在data()里初始化innerText: this.value - 模板中用
v-html="innerText"渲染,同时给容器加@input="onInput"和@blur="onBlur" -
onInput中调用this.$emit('input', this.$refs.editable.innerHTML)(注意:不能直接改this.innerText,否则下次value更新时会覆盖) - 用
watch监听value变化,同步更新this.innerText,保证父组件驱动也能生效
为什么不用 .sync 修饰符
.sync 是语法糖,本质是 :my-prop="foo" @update:my-prop="val => foo = val"。但它要求子组件主动触发 update:xxx 事件,且 Vue 2.3+ 之后不再允许直接修改 props —— 如果你在子组件里写了 this.myProp = newVal,控制台会警告并忽略。
容易踩的坑:
- 误以为加了
.sync就自动双向,结果子组件没 emitupdate:xxx,父组件收不到更新 - 在
watch里直接赋值this.myProp,触发 Vue 的 props 不可变警告,逻辑中断 - 多个 prop 同时用
.sync,事件名写错(比如写成@update:myage而不是@update:my-age),静默失败
原生 JS 实现简易双向绑定的关键路径
脱离框架时,关键不是监听 DOM,而是建立“属性变更 → 视图更新”和“视图事件 → 属性变更”的闭环。最简模型只需要三件事:一个存储状态的对象、一个绑定函数、一套事件代理机制。
例如用 Object.defineProperty 劫持属性:
const data = { msg: '' };
Object.defineProperty(data, 'msg', {
get() { return this._msg; },
set(val) {
this._msg = val;
document.getElementById('output').textContent = val;
document.getElementById('input').value = val;
}
});
但要注意:
- 无法监听新增属性(ES6 之前)
- 数组索引赋值(
arr[0] = x)不会触发set - 必须手动把 DOM 引用和更新逻辑耦合进
set,扩展性差
真正麻烦的从来不是“怎么让输入框变”,而是“怎么让任意属性变化都能通知所有依赖它的 DOM 节点”。这正是现代响应式系统要解决的核心问题——而你写的每个 v-model 或 watch,背后都绕不开这个权衡。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











