父子组件之间的数据双向绑定是Vue.js的一大特色,它让数据的传递变得更加方便。但是其实现方式及原理在Vue2和Vue3中却存在着一定的差异。我们在项目或者面试中或许经常看到.sync修饰符,v-model,以及Vue3新特性defineModel。这些似乎都和数据双向绑定有关,但是非常容易混淆和错用。本文将详细介绍在Vue2和Vue3中这几种方式的区别及使用场景。
1.v-model
(1)Vue2实现
在Vue2中,v-model 实际上 等于 :value + @input。
因此在其子组件上需要使用 props 接收名为 value 的参数,并使用 $emit 来触发名为 input 的事件。
由于 value 和 input 的写法固定,故在一个标签上只能使用一个 v-model。
<!-- Parent 组件-->
<template>
<div id="parent">
<h1>parent组件中的值:{{ age }}</h1>
<Child v-model="age"></Child>
<!-- 等同于: -->
<!-- <Child :value="age" @input="age = $event"></Child> -->
</div>
</template>
<script>
import Child from "@/components/Child.vue";
export default {
data() {
return {
age: 18
}
},
components: {Child}
}
</script>
<!-- Child 组件-->
<template>
<div class="child">
<h1>child组件中的值:{{ value }}</h1>
<button @click="$emit('input',value+1)">点我+1</button>
</div>
</template>
<script>
export default {
props: {value:Number}
}
</script>
(2)Vue3实现
在Vue3中,v-model后面可以指定传递的参数名,如 v-model:参数名=”参数值”。在不指定参数名时默认为 v-model:modelValue=”参数值”。
v-model:参数名 实际上又等于 :参数名 + @update:参数名
因此在其子组件上需要使用 defineProps 接收名为“参数名”的参数,并使用 defineEmits 来触发名为 update:参数名 的事件。
由于可以自定义参数名,故在一个标签上可以使用多个 v-model,但要注意参数名不要重复。
<!-- Parent 组件-->
<template>
<div id="app">
<h1>parent组件中的值:{{ age }}</h1>
<Child v-model="age"></Child>
<!-- 等同于: -->
<!-- <Child v-model:modelValue="age"></Child> -->
<!-- 等同于: -->
<!-- <Child :modelValue="age" @update:modelValue="age = $event"></Child> -->
</div>
</template>
<script setup>
import Child from "@/components/Child.vue";
import { ref } from 'vue';
const age = ref(18)
</script>
<!-- Child 组件-->
<template>
<h1>child组件中的值:{{ modelValue }}</h1>
<button @click="emit('update:modelValue', modelValue + 1)">点我+1</button>
</template>
<script setup>
defineProps({modelValue: Number})
const emit = defineEmits()
</script>
2.sync修饰符
.sync修饰符只在Vue2中,Vue3中已不再支持。
:参数名.sync 实际上等于 :参数名 + @update:参数名
因此在其子组件上需要使用 props 接收名为“参数名”的参数,并使用 $emit 来触发名为 update:参数名 的事件。
由于可以自定义参数名,故在一个标签上可以使用多个.sync修饰符来实现多个数据的双向绑定。
<!-- Parent 组件-->
<template>
<div id="parent">
<h1>parent组件中的值:{{ age }}</h1>
<Child :value.sync="age"></Child>
<!-- 等同于 -->
<!-- <Child :value="age" @update:value="age = $event"></Child> -->
</div>
</template>
<script>
import Child from "@/components/Child.vue";
export default {
data() {
return {
age: 18
}
},
components: {Child}
}
</script>
<template>
<div class="child">
<h1>child组件中的值:{{ value }}</h1>
<button @click="$emit('update:value',value+1)">点我+1</button>
</div>
</template>
<script>
export default {
props: {value:Number}
}
</script>
3.defineModel
defineModel是Vue3.4版本正式推出的新特性。它的出现可以极大的简化父子组件数据双向绑定的步骤,在子组件中将不再需要编写 defineProps 和 defineEmits。
<!-- parent 组件 -->
<template>
<h1>parent组件中的值:{{ age }}</h1>
<Child v-model="age"></Child>
<!-- 等同于 -->
<!-- <Child v-model:modelValue="age"></Child> -->
<!-- 等同于 -->
<!-- <Child :modelValue="age" @update:modelValue="age = $event"></Child> -->
</template>
<script lang="ts" setup>
import Child from "@/components/Child.vue";
import { ref } from 'vue';
const age = ref(18)
</script>
<!-- 子组件 -->
<template>
<div class="child">
<h1>child组件中的值:{{ age }}</h1>
<button @click="age = age + 1">点我+1</button>
</div>
</template>
<script lang="ts" setup>
const age = defineModel()
// 等同于
// const age = defineModel('modelValue') // 不传入参数即为默认值 modelValue
</script>
总结:在 Vue2 中,v-model 和 .sync修饰符的区别在于前者只能有一个,后者可以有多个;在 Vue3 中,移除了.sync修饰符,该功能可在 v-model 上加一个参数代替,同样可以有多个 v-model,其原理和.sync修饰符基本相同;而defineModel 是3.4版本新出的特性,也支持多个 v-model 进行数据双向绑定,且语法上更为简单。






