contenteditable 元素无 value 属性,v-model 失效;需手动处理 input/blur 事件、同步 textcontent/innerhtml 并恢复光标,子组件须声明 value prop 并 emit input 或 update:value 事件。

contenteditable 元素没有 value 属性,v-model 会失效
Vue 的 v-model 默认依赖元素的 value 属性和原生 input 事件。但 contenteditable="true" 的 <div> 没有 <code>value,它的内容存在 textContent 或 innerHTML 中。直接写 <div contenteditable v-model="text"></div>,Vue 会静默忽略绑定,或在 dev 模式下报 warning:Failed to resolve directive: model。
常见错误现象包括:
- 输入时光标跳回开头
- 拼音输入法下文字反复重绘、无法组词
- 父组件数据不变,但子组件视图“看起来”在变(其实是浏览器原生编辑行为)
@input 事件触发太频繁,直接更新 innerHTML 会破坏光标位置
监听 @input 确实能捕获每次按键,但若在 handler 里立刻执行 this.$emit('input', e.target.innerHTML) 并让父组件用 v-html 回填,就会导致 DOM 被 Vue 全量替换——浏览器原生光标位置丢失,用户无法连续输入。
关键点在于:
-
v-html是一次性渲染,不保留 selection/range - 必须用
textContent+ 手动恢复光标,或改用Range+SelectionAPI 同步位置 - 更稳妥的做法是:只在
@blur或防抖后的@input中同步数据,避免高频更新
innerHTML 和 textContent 的语义差异直接影响绑定逻辑
你绑定的是纯文本,还是带格式的 HTML?这决定了子组件该 emit 哪个值:
- 要富文本(如加粗、列表)→ emit
e.target.innerHTML,但必须清洗:过滤style、class、data-属性,只留<strong></strong>、<ul></ul>等语义标签 - 只要纯文本 → emit
e.target.textContent,避免 XSS 风险,也绕过换行标签(<div> vs <code><p></p>)的浏览器差异 - 父组件接收后,若用
v-html渲染,需确保服务端/前端已做 XSS 过滤;若用{{ }},则永远拿不到格式 - 声明
props: { value: String } - 在 template 中用
v-html="value"(或textContent动态设置)初始化内容 - 监听
@input,但 emit 的事件名必须是input(Vue 2)或update:value(Vue 3) - 不能在子组件 data 里缓存一份
innerText并双向绑定它——那只是镜像,不是源头
父子组件间 v-model 的“语法糖”需要手动对齐
Vue 对自定义组件的 v-model 是语法糖,等价于 :value="xxx" @input="val => xxx = val"。所以子组件必须:
最容易被忽略的一点:contenteditable 元素首次聚焦时可能不触发 input,空内容下点击也不一定触发 focus,建议初始化时插入一个零宽空格 或
<br>来稳定光标行为。











