Vue 3 中包装 v-model 的核心是封装 prop 接收与事件派发为可复用响应式引用;推荐 Vue 3.4+ 的 defineModel() 宏,支持类型推导和自定义名;旧版可用 ref + watch 手动封装,或抽离为 useVModel 组合式函数。

在 Vue 3 组合式函数中包装 v-model 双向状态,核心是把“接收 prop 值 + 触发更新事件”这两件事封装成一个可复用的响应式引用,而不是每次都在组件里重复写 defineProps、defineEmits 和 computed 或 ref 中间层。
用 defineModel() 宏(推荐,Vue 3.4+)
这是目前最简洁、语义最清晰的方式。它直接返回一个具备读写能力的 ref,底层自动处理 prop 接收和事件派发:
- 无需手动声明
modelValueprop 和update:modelValueemit - 支持类型推导(TS 友好),也支持自定义名称(如
defineModel('checked')) - 可直接绑定到原生元素:
示例:
<script setup><br>import { defineModel } from '<a style="color:#f60; text-decoration:underline;" title= "vue"href="https://m.php.cn/zt/15721.html" target="_blank">vue'<br><br>const model = defineModel() // 默认绑定 modelValue<br>// const checked = defineModel('checked') // 自定义字段名<br></script>手动封装:接收 prop 并返回可写的 ref(兼容旧版或需精细控制)
当项目尚未升级到 3.4,或你需要添加校验、防抖、转换逻辑时,可用 ref + watch 封装:
- 用
ref(props.modelValue)初始化本地状态 - 监听父组件 prop 变化,同步更新本地 ref(应对异步更新或外部重置)
- 暴露一个 setter 方法,触发
emit('update:modelValue', value)
示例:
<script setup><br>import { defineProps, defineEmits, ref, watch } from 'vue'<br><br>const props = defineProps({ modelValue: [String, Number, Boolean] })<br>const emit = defineEmits(['update:modelValue'])<br><br>const localValue = ref(props.modelValue)<br><br>watch(() => props.modelValue, (val) => {<br> localValue.value = val<br>})<br><br>function setValue(val) {<br> localValue.value = val<br> emit('update:modelValue', val)<br>}<br></script>进阶:支持修饰符(如 .trim、.number)或自定义行为
若需处理修饰符,需显式接收 modelModifiers prop,并在 setter 中判断:
- 声明
modelModifiers: { default: () => ({}) }prop - 在
setValue中检查props.modelModifiers.trim等布尔值并做预处理 - 仍通过
emit('update:modelValue', processedValue)向上同步
注意:修饰符仅在父组件使用 v-model.trim 时才传入,不是所有场景都需要。
组合式函数抽离(真正复用的关键)
把上述逻辑进一步封装为独立函数,例如 useVModel:
const value = ref(props[key])
watch(() => props[key], (v) => (value.value = v))
return {
value,
set(v) {
value.value = v
emit(`update:${key}`, v)
}
}
}
子组件中调用:const { value, set } = useVModel(props, emit, 'checked'),后续即可统一操作。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










