父子组件之间的数据双向绑定是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 进行数据双向绑定,且语法上更为简单。