Vue 2 和 Vue 3 中实现组件之间自定义组件双向绑定

一、Vue 2 中的实现

1. 使用 v-model(默认 value + input
子组件 CustomInput.vue

<template><input v-model="internalValue" />
</template><script>
export default {props: ['value'], // Vue 2 的 v-model 默认 prop 是 valuecomputed: {internalValue: {get() {return this.value;},set(val) {this.$emit('input', val); // Vue 2 的默认事件是 input}}}
}
</script>

 父组件 

<template><CustomInput v-model="message" /><p>父组件数据:{{ message }}</p>
</template><script>
export default {data() {return {message: 'Hello Vue 2!'}}
}
</script>
2. 使用 .sync 修饰符(多属性双向绑定)
子组件
<template><input v-model="internalTitle" v-model="internalNums"/>
</template><script>
export default {props: ['title','nums'], // 接收父组件传递的 titlecomputed: {internalTitle: {get() {return this.title;},set(val) {this.$emit('update:title', val); // 触发 update:title 事件}},internalNums: {get() {return this.nums;},set(val) {this.$emit('update:nums', val); // 触发 update:nums 事件}}}
}
</script>
父组件
<template><CustomInput :title.sync="pageTitle" :nums.sync="pageNums"/><p>当前标题:{{ pageTitle }}</p><p>当前nums:{{ pageNums }}</p>
</template><script>
export default {data() {return {pageTitle: '默认标题',pageNums:'默认Nums',}}
}
</script>

二、Vue 3 中的实现

1. 默认 v-modelmodelValue + update:modelValue
子组件 CustomInput.vue
<script>
export default {props: ['modelValue'], // Vue 3 的 v-model 默认 propemits: ['update:modelValue'], // 显式声明事件computed: {value: {get() {return this.modelValue;},set(val) {this.$emit('update:modelValue', val);}}}
}
</script><template><input v-model="value" />
</template>
父组件
<template><CustomInput v-model="message" /><p>父组件数据:{{ message }}</p>
</template><script setup>
import { ref } from 'vue';
const message = ref('Hello Vue 3!');
</script>
2. 多 v-model 绑定(如 v-model:title
子组件

<template><input v-model="internalTitle" v-model="internalNums" />
</template><script>
export default {props: ['title'],emits: ['update:title','update:nums'],computed: {internalTitle: {get() {return this.title;},set(val) {this.$emit('update:title', val);}},internalNums: {get() {return this.nums;},set(val) {this.$emit('update:nums', val);}}}
}
</script>
父组件
<template><CustomInput v-model:title="pageTitle" v-model:nums="pageNums"/><p>当前标题:{{ pageTitle }}</p><p>当前标题:{{ pageNums }}</p>
</template><script setup>
import { ref } from 'vue';
const pageTitle = ref('默认title');
const pageNums = ref('默认nums');</script>

三、关键区别总结

特性Vue 2Vue 3
默认 prop 名valuemodelValue
默认事件名inputupdate:modelValue
多属性双向绑定需用 .sync 修饰符直接 v-model:arg(如 v-model:title
事件声明无 emits 选项需显式声明 emits
Composition API不支持 <script setup>支持 <script setup> 简化写法

四、通用最佳实践

  1. 始终通过 $emit 更新数据
    禁止在子组件中直接修改 prop(违反单向数据流)。

  2. 复杂数据验证
    在计算属性的 setter 中添加校验逻辑:

    set(val) {if (val.length <= 100) { // 示例:输入长度限制this.$emit('update:modelValue', val);}
    }
  3. TypeScript 增强(Vue 3)

    <script setup lang="ts">
    const props = defineProps<{ modelValue: string }>();
    const emit = defineEmits<{ (e: 'update:modelValue', value: string): void }>();const value = computed({get: () => props.modelValue,set: (val) => emit('update:modelValue', val)
    });
    </script>

五、常见问题

Q:为什么 Vue 3 改用 modelValue
  • 答案:避免与原生 HTML 元素的 value 属性冲突,同时支持多 v-model 绑定。

Q:Vue 2 能兼容 Vue 3 的写法吗?
  • 答案:部分兼容(Vue 2.7+ 支持 emits 声明但不强制),但默认行为仍需遵循 Vue 2 规则。


通过以上对比,你可以根据项目使用的 Vue 版本选择对应的实现方式。如果需要更复杂的场景(如防抖、格式化),可以在计算属性的 setter 中扩展逻辑。