Vue 中 v-model 的原理和自定义 v-model 怎么用?
📅 2026/7/30 10:31:43
👁️ 阅读次数
📝 编程学习
Vue 中 v-model 的原理和自定义 v-model 怎么用?
一句话总结:v-model 本质是「
:value+@input」的语法糖,自定义组件用modelValue+update:modelValue即可实现双向绑定,多个 v-model 用具名参数搞定!
正文目录
- v-model 到底做了什么?
- 5 大高频踩坑场景 & 修复代码
- 万能兜底:props + emit + computed 代理
- 预防 checklist(不再踩坑)
- 一句话总结
一、v-model 到底做了什么?
原生表单元素上用 v-model:
<template> <input v-model="msg" /> </template>等价于:
<template> <input :value="msg" @input="msg = $event.target.value" /> </template>核心:v-model = 绑定 value + 监听 input 事件。这不是魔法,是语法糖。
自定义组件上用 v-model(Vue 3):
<!-- 父组件 --> <template> <MyInput v-model="msg" /> </template>等价于:
<template> <MyInput :modelValue="msg" @update:modelValue="msg = $event" /> </template>Vue 3 的变化:默认 prop 名从value改为modelValue,事件名从input改为update:modelValue。
二、5 大高频踩坑场景 & 修复代码
① 自定义组件直接修改 modelValue
<!-- ❌ 子组件直接改 prop,触发 Avoid mutating a prop 警告 --> <script setup> const props = defineProps(['modelValue']); const updateText = (e) => { props.modelValue = e.target.value; // ❌ 直接改 prop }; </script> <template> <input :value="modelValue" @input="updateText" /> </template>修复:用 emit 通知父组件
<!-- ✅ 子组件用 emit 通知父组件修改 --> <script setup> const props = defineProps(['modelValue']); const emit = defineEmits(['update:modelValue']); const updateText = (e) => { emit('update:modelValue', e.target.value); // ✅ emit 通知 }; </script> <template> <input :value="modelValue" @input="updateText" /> </template>② 用 v-model 绑定对象的属性,父组件不更新
<!-- ❌ 子组件修改了对象属性,但父组件的 v-model 没反应 --> <script setup> const props = defineProps(['modelValue']); const handleClick = () => { props.modelValue.name = 'new name'; // ❌ 直接改对象属性,不触发 update }; </script>修复:emit 一个新对象
<!-- ✅ emit 新对象,触发响应式更新 --> <script setup> const props = defineProps(['modelValue']); const emit = defineEmits(['update:modelValue']); const handleClick = () => { emit('update:modelValue', { ...props.modelValue, name: 'new name' }); // ✅ 新对象 }; </script>③ 一个组件需要多个 v-model
<!-- ❌ 只有一个 v-model,其他属性只能用 prop + emit 手动传 --> <template> <UserForm v-model="user" :name="name" @update:name="name = $event" /> </template>修复:Vue 3 支持多个具名 v-model
<!-- ✅ 多个 v-model 用具名参数 --> <template> <UserForm v-model:name="name" v-model:age="age" v-model:email="email" /> </template><!-- 子组件 --> <script setup> defineProps(['name', 'age', 'email']); defineEmits(['update:name', 'update:age', 'update:email']); </script>④ v-model 绑定到computed,但没写 setter
<script setup> import { computed } from 'vue'; const props = defineProps(['modelValue']); const emit = defineEmits(['update:modelValue']); // ❌ 只读 computed 没有 setter,v-model 写入时报错 const proxy = computed(() => { return props.modelValue.toUpperCase(); }); </script> <template> <input v-model="proxy" /> <!-- ❌ 没有 setter,无法写入 --> </template>修复:computed 写 get + set
<script setup> import { computed } from 'vue'; const props = defineProps(['modelValue']); const emit = defineEmits(['update:modelValue']); // ✅ computed 带 get/set,代理 v-model const proxy = computed({ get() { return props.modelValue; }, set(val) { emit('update:modelValue', val); } }); </script> <template> <input v-model="proxy" /> <!-- ✅ 读写都通 --> </template>⑤ 修饰符 .trim / .number / .lazy 在自定义组件上不生效
<!-- ❌ 父组件用了修饰符,但子组件没处理 --> <template> <MyInput v-model.trim="msg" /> <!-- .trim 没有效果 --> </template>修复:子组件接收 modelModifiers 并处理
<!-- ✅ 子组件处理修饰符 --> <script setup> const props = defineProps({ modelValue: String, modelModifiers: { default: () => ({}) } // ✅ 接收修饰符对象 }); const emit = defineEmits(['update:modelValue']); const handleInput = (e) => { let val = e.target.value; if (props.modelModifiers.trim) { val = val.trim(); // ✅ 手动处理 .trim } emit('update:modelValue', val); }; </script> <template> <input :value="modelValue" @input="handleInput" /> </template>三、万能兜底:props + emit + computed 代理
| 场景 | 方案 | 关键点 |
|---|---|---|
| 基本双向绑定 | defineProps+defineEmits | prop 只读,emit 通知 |
| 需要转换值 | computed get/set | get 读 prop,set emit |
| 多个双向绑定 | 具名 v-model | v-model:xxx |
| 对象属性绑定 | emit 新对象 | { ...old, key: newVal } |
| 修饰符处理 | modelModifiers | 手动 trim/number/lazy |
万能模板:
<!-- 父组件 --> <script setup> import { ref } from 'vue'; import CustomInput from './CustomInput.vue'; const msg = ref(''); const user = ref({ name: '', age: 0 }); </script> <template> <!-- 基本用法 --> <CustomInput v-model.trim="msg" /> <!-- 对象用法 --> <CustomForm v-model="user" /> </template><!-- 子组件 CustomInput.vue --> <script setup> import { computed } from 'vue'; const props = defineProps({ modelValue: String, modelModifiers: { default: () => ({}) } }); const emit = defineEmits(['update:modelValue']); // ✅ computed 代理,处理修饰符 const proxy = computed({ get() { return props.modelValue; }, set(val) { if (props.modelModifiers.trim) val = val.trim(); emit('update:modelValue', val); } }); </script> <template> <input v-model="proxy" /> </template>四、预防 checklist
- 自定义组件 v-model永远不要直接改 modelValue prop,用 emit
- 需要转换值时用computed get/set代理,别手动写 input 事件
- 多个双向绑定用具名 v-model(
v-model:xxx),不要叠一堆 prop + emit - 绑定对象属性时emit 新对象,不要直接改原对象属性
- 修饰符在自定义组件上需要手动处理,子组件接收
modelModifiers - Vue 2 和 Vue 3 的 v-modelprop 名不同(value vs modelValue),迁移注意
- 表单组件封装时,先写 computed 代理再绑 v-model,一步到位
五、一句话总结
「v-model =
:value+@input的语法糖」,自定义组件用modelValue+update:modelValue实现双向绑定,需要转换值就上 computed get/set,多个绑定就用具名 v-model。记住:prop 只读,永远 emit!
最后问候亲爱的朋友们,并邀请你们阅读我的全新著作
📚 《Vue.js 3企业级项目开发实战(微课视频版》
编程学习
技术分享
实战经验