v-model 这个知识点既基础又容易翻车。我面试前端岗位的时候几乎每一轮都会被问到“v-model 的原理是什么”而且追问越来越细从“语法糖”三个字一路问到编译产物。很多候选人能说出“v-model 是语法糖”但再往深一层问到组件上它编译成什么、Vue2 和 Vue3 有什么区别、多个 v-model 怎么绑、修饰符怎么自定义能讲清楚的人就少了一大半。这篇文章我不打算写成教科书式的原理说明而是按我自己的理解从最底层的原生 DOM 事件讲起再对照 Vue2 和 Vue3 的实际写法最后给出一套可以直接抄作业的自定义 v-model 组件封装方案。不管你是准备面试还是要在项目里封装带 v-model 的通用组件这篇文章应该都能帮你把这层窗户纸捅破。1. 剥开 v-model它到底是怎么工作的1.1 先看原生输入框双向绑定从哪来要理解 v-model先得搞清楚“双向绑定”在原生 HTML 里长什么样。拿一个普通的输入框来说你在页面里看到的“数据联动”本质上就是两件事把 JS 变量的值塞进输入框的 value 属性再监听输入框的 input 事件把新值写回变量。用原生 JS 写的话大概是这样的input typetext idname / script const input document.getElementById(name) let name 张三 // 把变量的值放到输入框里 input.value name // 监听输入事件把新值写回变量 input.addEventListener(input, (e) { name e.target.value }) /script这段代码看着简单但这就是“双向绑定”的全部秘密数据到视图是一条赋值视图到数据是一条事件监听两条路拼在一起形成了闭环。v-model 干的事情就是帮我们把这两行重复劳动省掉用一句指令替代。在 Vue2 和 Vue3 里给原生 input 元素写v-model模板编译器都会把它展开成value属性加input事件监听最终生成的运行时代码本质上跟上面这段原生 JS 一模一样只不过换成响应式变量的赋值逻辑。记住这一点后面理解组件上的 v-model 就有基础了。1.2 组件上的 v-model从 props 到事件的闭环原生元素上的 v-model 好理解因为 input、textarea 这些元素本身就自带 value 属性和 input 事件。但组件不是真实 DOM它没有默认的 value、没有默认的 input 事件那 v-model 在组件上是怎么起作用的答案是组件上的 v-model其实是一种“父组件与子组件之间的数据协议”。父组件通过 props 把一个值传给子组件子组件内部不能直接改这个 props必须通过$emit触发一个事件把新值抛给父组件由父组件自己更新数据。这个过程走完一轮双向绑定就成立了。Vue2 的默认协议是这样的!-- 父组件使用 -- my-input :valuename inputname $event / !-- 等于 -- my-input v-modelname /也就是说Vue2 里组件上的v-model默认展开成:value加input。子组件需要声明一个value的 prop然后在需要更新数据时$emit(input, newValue)。Vue3 把协议换了个名字默认是modelValue加update:modelValuemy-input :modelValuename update:modelValuename $event / !-- 等于 -- my-input v-modelname /子组件里声明modelValue这个 prop更新时$emit(update:modelValue, newValue)。本质没有变还是 props 加事件这套双向协议只是名字变了灵活性大幅提升。1.3 为什么组件 v-model 的命名要变Vue3 的取舍很多初学者会问Vue3 好好的干嘛把value改成modelValue这不是没事找事吗其实这个改动非常关键它解决了一个 Vue2 里长期存在的尴尬问题v-model 默认占用了value这个 prop 名如果你的组件业务上确实需要一个valueprop 来做别的事就会跟 v-model 冲突。举个真实例子。我在 Vue2 项目里封装过一个表格组件某个字段确实叫value但表格同时还需要支持一个可编辑单元格的 v-model。这时候value这个名字就被占了v-model 还得用我只能靠model选项去改 v-model 的绑定 prop 名export default { model: { prop: content, event: change } }这种写法能解决问题但每次封装组件都得记得配置一遍不统一也不直观。Vue3 直接把默认名改成modelValue相当于给 v-model 腾出了一块独立的“专属车道”组件内部的其他业务 prop 就不会被它挤占到。而且这个改动为后面“多个 v-model 并存”扫清了障碍因为每个 v-model 都可以绑定到不同的 prop 和事件上。2. Vue2 与 Vue3 的 v-model 差别一张表讲清楚2.1 默认属性和事件对照刚才已经提到了默认协议的区别这里把关键差异汇总成一张对照表方便随时查阅对比项Vue2Vue3组件默认绑定 propvaluemodelValue组件默认事件inputupdate:modelValue原生 input 元素value inputvalue input内部逻辑一致多个值绑定借助 .sync 修饰符v-model:propName内置修饰符.trim / .number / .lazy.trim / .number / .lazy自定义修饰符不支持支持通过 props 接收自定义默认协议配置 model 选项无需配置用 v-model:xxx 指定defineModel 宏不支持Vue 3.4 支持先说清楚一点Vue3 里原生元素上的 v-model 展开逻辑跟 Vue2 是一样的。输入框还是使用value加input复选框用的是checked加change下拉框用的是value加change。这些细节很多人不注意面试被问到就容易卡住。组件上的差异才是重点。Vue2 里 v-model 只能使用一套协议默认value input想改就只能通过model选项。Vue3 里没有这个限制每个 v-model 都可以用v-model:xxx的形式单独指定 prop 和事件协议本身变成了开放式的组件和父组件之间可以约定多套绑定关系。2.2 多个 v-model从 .sync 到 v-model:xxx 的进化Vue2 里一个组件只能有一个 v-model那如果一个组件需要同时双向绑定多个数据怎么办Vue2 提供了.sync修饰符作为补充。比如一个弹窗组件要同时控制“显示/隐藏”和“标题文字”可以这样写dialog-window :visible.syncdialogVisible :title.syncdialogTitle /这个写法的原理也是语法糖展开以后等价于dialog-window :visibledialogVisible update:visibledialogVisible $event :titledialogTitle update:titledialogTitle $event /子组件里更新的时候需要手动$emit(update:visible, false)、$emit(update:title, 新标题)这样去触发事件。能用但.sync和v-model是两套并行的写法一套是“约定俗成的 input 事件”一套是“显式指定 update:propName 事件”心智负担比较重。Vue3 直接把这套机制统一了。v-model:visible的展开结果跟 Vue2 的.sync几乎一致都是绑定 prop 加监听update:propName事件但写法更干净也不需要再记“什么时候用 v-model、什么时候用 .sync”的规则。从此 v-model 和 .sync 合并成一个东西只有一个心智模型组件绑定某个值事件驱动父组件更新它。2.3 修饰符的差异自定义修饰符才是 Vue3 的重头戏Vue2 里 v-model 有.trim、.number、.lazy三个内置修饰符它们作用于原生元素或者组件时Vue 内部直接做了处理。问题在于如果你封装了自己的组件想在组件上也用.trimVue2 是不会主动帮你处理的。组件的 v-model 拿到的是什么值子组件就得自己处理修饰符的支持非常有限。Vue3 补上了这块短板不仅原生元素能用修饰符自定义组件也能识别修饰符。原理是Vue 会把修饰符信息一并打包通过一个以 “Modifiers” 结尾的 prop 传给子组件。比如my-input v-model.trimname /在子组件里可以取到一个modelModifiers的 prop它的值是一个对象包含所有修饰符标记props: { modelValue: String, modelModifiers: { default: () ({}) } }在这个例子里modelModifiers的值就是{ trim: true }。子组件在emit更新值之前先自己判断修饰符并做处理然后才把处理好的值抛出去。这让“自定义修饰符”成为可能你甚至可以发明自己的修饰符比如.capitalize实现首字母大写后面第三章我会给出完整代码。这里值得留意的是修饰符 prop 的命名规则默认 v-model 的修饰符 prop 是modelModifiers如果是v-model:title那修饰符 prop 就是titleModifiers命名永远是prop名 Modifiers的驼峰形式。3. 手把手封装一个支持 v-model 的组件3.1 Vue2 版本value 加 input 的经典写法下面我以一个“可清除输入框”为例演示在 Vue2 中如何封装一个支持 v-model 的组件。功能很简单输入框右侧有一个清空按钮点击后清空内容输入时实时同步数据。template div classclearable-input input :valuevalue input$emit(input, $event.target.value) placeholder请输入内容 / button v-ifvalue click$emit(input, ) 清空 /button /div /template script export default { name: ClearableInput, props: { value: { type: String, default: } } } /script这个组件的用法很直观clearable-input v-modelkeyword /在这个实现里input事件从原生输入框那里接收用户输入再重新$emit给父组件清空按钮直接$emit(input, )把空字符串抛回父组件。所有的数据流转都走“props 进、事件出”这条单行道没有直接改动 props符合 Vue2 的规范。如果项目中确实要用非value的名字就需要配置model选项export default { model: { prop: content, event: change }, props: { content: { type: String, default: } } }这么写之后使用方式不变还是v-modelxxx但内部绑定的是content和change。这也是 Vue2 面试题里比较喜欢问的一个点怎么自定义 v-model 的默认绑定答案就是model选项。3.2 Vue3 版本modelValue 加 update:modelValue 的新写法同样的组件在 Vue3 里可以这样实现。如果你用的是script setup代码会非常简洁template div classclearable-input input :modelValuemodelValue input$emit(update:modelValue, $event.target.value) placeholder请输入内容 / button v-ifmodelValue click$emit(update:modelValue, ) 清空 /button /div /template script setup defineProps({ modelValue: { type: String, default: } }) defineEmits([update:modelValue]) /script这里有个细节新手经常忽略在模板里我直接用$emit触发事件没有在script setup中单独声明defineEmits的话其实也能跑但在 Vue3 里建议明确声明事件这样代码可读性更好也能避免原生事件透传时的意外干扰。像上面这样显式声明组件就没有把多余的监听器透传到根元素上行为更可控。如果项目里用的是 Vue 3.4 及以上版本还有更省事的写法defineModel。template div classclearable-input input v-modelmodel placeholder请输入内容 / button v-ifmodel clickmodel 清空 /button /div /template script setup const model defineModel({ type: String, default: }) /scriptdefineModel是 Vue 3.4 引入的编译宏它会自动帮你声明modelValueprop 和update:modelValue事件返回的是一个可以读写的 ref。模板里直接v-modelmodel内部赋值直接改model都会自动映射到父组件的值更新上封装成本几乎降到零。3.3 多值绑定加自定义修饰符的一个完整实战前面讲的都是单个 v-model更贴近真实业务的是多个 v-model 并存。我封装过一个“搜索面板”组件需要同时控制面板的打开状态、搜索关键词和选中分类这正好用上 Vue3 的多 v-model 能力template div v-ifvisible classsearch-panel input :valuekeyword input$emit(update:keyword, $event.target.value) placeholder搜索关键词 / select :valuecategory change$emit(update:category, $event.target.value) option valueall全部分类/option option valuetech科技/option option valuelife生活/option /select button click$emit(update:visible, false)关闭/button /div /template script setup defineProps({ visible: Boolean, keyword: { type: String, default: }, category: { type: String, default: all } }) defineEmits([update:visible, update:keyword, update:category]) /script父组件使用的时候可以一次性绑定三个 v-modelsearch-panel v-model:visiblepanelVisible v-model:keywordsearchKeyword v-model:categorysearchCategory /这种写法解决了 Vue2 时代这类组件用.sync才能实现的痛点而且语义更清晰看到v-model:keyword就知道这个 prop 是双向绑定的看到v-model:visible就知道是控制显示状态的。接着聊自定义修饰符。我在一个表单项目里需要“首字母自动大写”的输入框用 Vue3 自定义修饰符实现非常顺手template input :modelValuemodelValue inputhandleInput blur$emit(update:modelValue, modelValue.trim()) / /template script setup const props defineProps({ modelValue: { type: String, default: }, modelModifiers: { default: () ({}) } }) const emit defineEmits([update:modelValue]) function handleInput(e) { let value e.target.value if (props.modelModifiers.capitalize) { value value.charAt(0).toUpperCase() value.slice(1) } emit(update:modelValue, value) } /script使用时加一个.capitalize修饰符capitalize-input v-model.capitalizeusername /每次用户输入组件都先做首字母大写处理再把结果抛给父组件。这个才是 Vue3 v-model 真正超越 Vue2 的地方v-model 不再是固定的语法糖而是变成了一整套可以被扩展的数据流协议从 prop 名、事件名到值处理函数都可以按需定制。4. 实战中容易踩的坑与面试回答要点4.1 直接修改 props最常见的翻车现场不管是 Vue2 还是 Vue3组件内部直接修改 props 的值都是不被推荐的。Vue2 里直接改this.value xxx开发环境下会看到一条警告“Avoid mutating a prop directly since the value will be overwritten”。Vue3 里同样有类似的警告而且严格模式下还会直接报错。正确的思路永远是子组件不要拥有修改 props 的权力只负责抛事件由父组件决定是否更新数据。拿 v-model 来说子组件要更新值唯一正规的方式就是$emit(update:modelValue, newValue)。这样做的好处是整个数据流是单向的、可追踪的你不会在项目里遇到“数据莫名其妙被改了”的灵异事件。我见过不少新手用 watch 监听 props 再同步到本地 data本意是想让数据可编辑结果 watch 和 v-model 同时作用出现无限循环或更新不同步。如果你有这个冲动先停下来想想这个值是不是真的需要在子组件里做“本地拷贝”大多数情况下直接用computed包一层带 setter 的属性会更干净const value computed({ get: () props.modelValue, set: (val) emit(update:modelValue, val) })4.2 原生元素与组件 v-model 混用时的细节差异项目里常见的另一个坑是在封装一层组件时把 v-model 直接转手绑到内部的原生 input 上。比如template input v-modelmodelValue / /template script setup defineProps([modelValue]) defineEmits([update:modelValue]) /script看起来没问题但实际运行时你会发现模板里的v-modelmodelValue会被编译成:valuemodelValue加input关键问题是modelValue是只读的 props你没法在 input 事件里给它赋值内部还会产生修改 props 的警告。正确的做法是写一个 computed 中转或者干脆手动绑定 value 和 input 事件template input :valuemodelValue input$emit(update:modelValue, $event.target.value) / /template还有一种情况是给自定义组件传原生属性比如my-input v-modelname placeholder请输入 /Vue3 里placeholder会自动落入$attrs透传到组件根节点。但如果根节点不是原生 input而是 div透传行为就会不符合预期。所以封装输入类组件时建议把根节点改成原生 input或者显式用inheritAttrs: false加上v-bind$attrs手动控制透传位置避免 placeholder、disabled 这些原生属性散落到奇怪的地方。4.3 面试这样答 v-model基本稳了面试的时候被问到 v-model我会建议按下面的顺序组织回答既体现深度又不会跑偏先强调本质v-model 是语法糖不是框架魔法。它底层是“props 进、事件出”的数据流协议只是通过模板编译帮我们省掉了手写绑定和监听的重复代码。再说 Vue2 和 Vue3 的区别Vue2 默认绑定valueprop 和input事件想改协议要走model选项Vue3 默认绑定modelValueprop 和update:modelValue事件并且支持多个 v-model 通过v-model:propName形式共存不再需要.sync。最后补上修饰符的差异化能力Vue2 只有内置的.trim、.number、.lazy不能自定义Vue3 支持自定义修饰符子组件通过modelModifiers接收修饰符标记并自行处理。如果项目用的版本够新还能提一下defineModel宏这个属于加分项说明你关注了 Vue 3.4 之后的新特性。按这个顺序回答从“是什么”到“有什么区别”再到“怎么扩展”基本覆盖了面试官想考察的所有深度而且全程有实际代码支撑不是空谈概念。我自己的体会是v-model 这个知识点特别适合拿来检验一个人对 Vue 数据流的理解程度。能把这个点讲透的人写复杂组件一般都不会乱因为 ta 心里清楚数据该从哪来、该往哪去不会修修补补地到处直接改状态。如果你正在准备面试不妨打开一个 Vue3 项目亲手封装一个有多个 v-model 和自定义修饰符的组件跑一遍比背十遍文档都有用。
企业数字化 ERP 产品动态
相关推荐
7系列FPGA配置实战:UG470、SPI与MultiBoot回退排错指南 简介:《ug470-7Series-Config-中文版-2025年.pdf》是一份AMD/Xilinx官方7系列FPGA配置用户指南的中文翻译版,主要面向FPGA开发工程师、硬件设计人员以及系统性学习FPGA配置技术的初学者,帮助读者理清配置接口选择、比特流生成与加载、配置安全… · 2026/9/23 15:53:33
Crossplane E2E 测试体系设计:从人工黑盒验证到每个 PR 自动回归 云原生后端 【免费下载链接】crossplane The Cloud Native Control Plane 项目地址: https://gitcode.com/gh_mirrors/cr/crossplane 点击查看 免费下载 本文围绕 Crossplane 仓库中的设计文档 design/one-pager-e2e-tests.md 展开,梳理 Crossplane 端到… · 2026/9/23 15:53:33
细粒度图像检索系统源码详解:从SIFT词包到深度哈希 简介:这是一份基于Python实现的细粒度图像检索系统源码包,面向图像检索方向的开发者、研究人员及需要输出技术汇报的工程师,核心解决细粒度图像精确匹配、多标签分类和特征表示学习等问题。包内共79个文件,整体约66.21MBÿ… · 2026/9/23 16:37:48
三角形四心坐标公式与性质:重心、内心、外心、垂心一次讲透 上个月给学生讲三角形四心的时候,我发现很多人记不住重心、内心、外心、垂心的公式,不是因为公式难,而是因为不知道它们从哪来、有什么用。你一旦把“中线交点”“角平分线交点”“中垂线交点”“高线交点”这四句话真正吃透,后面… · 2026/9/23 16:37:48
冰雪节发条新手避坑:3步搞定水利数据配置不再卡壳 冰雪节发条新手避坑:3步搞定水利数据配置不再卡壳 配置环境就卡半天?别急,这太正常了。很多刚接触【冰雪节发条】的水利工程师,一上来就被复杂的依赖关系搞得头大,明明照着教程敲代码,结果报错一堆,心态直接崩了。今天这篇【新手避坑】指南,就是专门… · 2026/9/23 16:37:41
AI Research Skills 之 Whisper:99 语言鲁棒语音识别与转写实战指南 AI 技能人工智能大模型深度学习 【免费下载链接】AI-Research-SKILLs Comprehensive open-source library of AI research and engineering skills for any AI model. Package the skills and your claude code/codex/gemini agent will be an AI research agent with full hor… · 2026/9/23 16:37:35
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29