Vue3组件数据双向绑定

发布于:2025-09-13 ⋅ 阅读:(19) ⋅ 点赞:(0)

目录

组件数据的双向绑定

利用组件通信机制手搓

使用defineModel语法

效果:

defineModel()研究

defineModel的底层机制

defineModel参数

ref选项

ref名字


组件数据的双向绑定

Vue3中的组件数据双向绑定是困扰作者许久的问题,今天因为公司需要,系统的学习了一遍,发现很多有趣的东西,为此在这里记录下来,希望可以帮助到同学们。

目前对于组件数据的双向绑定有两种形式:“利用组件通信机制手搓”(啰嗦,但有助于理解原理)、“使用defineModel语法”(Vue3.4+更新,方便快捷

利用组件通信机制手搓

  • defineProps:接收父组件的数据(正向更新)
  • defineEmits:向父组件传递数据(反向更新)

下面我们用一个“子组件封装一个输入框,父组件调用并实时显示数据”的例子来说明:

子组件(child.vue)

<script setup>
const props = defineProps({
    label:{
        type: String,
        default: "测试名"
    },
	modelValue:{
		type: String,
		default: "测试值"
	}
})

const emits = defineEmits(['update:modelValue'])

</script>

<template>
	<div>
		<span>{{ props.label }}</span>
		<input type="text" :value="props.modelValue" @input="emits('update:modelValue',$event.target.value)">
	</div>
</template>

<style scoped>
</style>

父组件(App.vue)

<script setup>
import { ref } from 'vue';
import child from './components/child.vue';

const inputValue = ref('');

</script>

<template>
<div class="container">
    <child 
        :modelValue="inputValue"
        @update:modelValue="inputValue = $event"
    />
    <p>{{ inputValue }}</p>
</div>
</template>

<style scoped>
</style>

注意

  • update:modelValue:这是Vue3里约定的更新数据的事件名,格式为“update:prop”,在这个代码中,propmodelValue,因此事件名为“update:modelValue
  • @input:当输入框数据发生变化时,向父组件传递数据

此时,你可能会发现,父组件要使用:“modelValue”和“update:modelValue”两处属性槽,比较啰嗦。那么此时就可以使用Vue3提供给我们的一个“v-model语法糖

<child 
    v-model="inputValue"
/>

上面的代码就相当于:

<child 
    :modelValue="inputValue"
    @update:modelValue="inputValue = $event"
/>

值得注意的是:“尽管父组件可以使用v-model省略,但是子组件的update:modelValue事件不可省略

使用defineModel语法

使用defineModel语法要求Vue版本大于3.4

子组件(child.vue)

<script setup>
const model = defineModel()
const props = defineProps({
    label:{
        type: String,
        default: "测试名"
    }
})
</script>

<template>
	<div>
		<span>{{ props.label }}</span>
		<input type="text" v-model="model" />
	</div>
</template>

<style scoped>
</style>

父组件(App.vue)

<script setup>
import { ref } from 'vue';
import child from './components/child.vue';

const inputValue = ref('');

</script>

<template>
<div class="container">
    <child 
        v-model="inputValue"
    />
    <p>{{ inputValue }}</p>
</div>
</template>

<style scoped>
</style>

效果:

可以看到使用defineModel()可以非常轻松地实现组件间的双向数据绑定

defineModel()研究

defineModel的底层机制

defineModel返回的值是一个ref

defineModel是一个便利宏,编译器会解析为:

  • 一个名为modelValue的prop,本地ref的值与其同步
  • 一个名为update:modelValue的事件,当本地ref的值发生变更时修改

大白话

  • defineModel返回一个ref,当这个ref值变化时,会自动调用defineModel解析的update:modelValue事件
  • defineModel解析的update:modelValue事件会传递当前ref的值

defineModel参数

ref选项

defineModel可以接收一个“对象”来配置ref的一些选项

// 该v-model必填
const model = defineModel({required: true})
// 默认值
const model = defineModel({default: 0})

注意

  • 当给model绑定一个“default默认值”时,如果父组件传入的v-model参数有值,那么该默认值会被顶替
  • 如果父组件传入的v-model参数为空,那么该默认值会生效,但父组件与子组件之间不同步,此时父组件值为空,子组件值为默认值

子组件(child.vue)

<script setup>
const model = defineModel({default: "123"})
const props = defineProps({
    label:{
        type: String,
        default: "测试名"
    }
})
</script>
<template>
	<div>
		<span>{{ props.label }}</span>
		<input type="text" v-model="model"/>
	</div>
</template>
<style scoped>
</style>

父组件(parent.vue)

<script setup>
import { ref } from 'vue';
import child from './components/child.vue';

const titleValue = ref();

</script>

<template>
<div class="container">
    <child 
        v-model="titleValue"
    />
    <p>{{ titleValue }}</p>
</div>
</template>

<style scoped>
</style>

效果

当组件初次载入时,此时父组件和子组件之间的数据不同步,当修改子组件数据后,父子组件数据同步

原因

  • 组件载入时,父组件向子组件传参数值,参数值为空,因此子组件默认值生效,子组件参数值为123
  • 子组件默认值生效后,没有向父组件传递参数值,此时父组件参数值仍为空
  • 当子组件参数值修改后,触发底层update:modelValue方法,向父组件传递参数值
  • 父组件接收到参数值,修改本地参数值,实现数据同步

ref名字

defineModel可以接收一个“字符串”作为返回ref的名字,利用该特性我们可以同时绑定多个v-model

用法:

// 指定一个title名字
const model = defineModel('title')
// 父组件调用时,指定传给名为title的ref
<child 
    v-model:title="inputValue"
/>

多个v-model绑定:

子组件(child.vue)

<script setup>
const modelTitle = defineModel('title')
const modelDescription = defineModel('description')
const props = defineProps({
    label:{
        type: String,
        default: "测试名"
    }
})
</script>
<template>
	<div>
		<span>{{ props.label }}</span>
		<input type="text" v-model="modelTitle" style="margin-right: 1rem"/>
		<input type="text" v-model="modelDescription" />
	</div>
</template>
<style scoped>
</style>

父组件(parent.vue)

<script setup>
import { ref } from 'vue';
import child from './components/child.vue';

const titleValue = ref('');
const descriptionValue = ref('');

</script>

<template>
<div class="container">
    <child 
        v-model:title="titleValue"
        v-model:description="descriptionValue"
    />
    <p>{{ titleValue }}</p>
    <p>{{ descriptionValue }}</p>
</div>
</template>

<style scoped>
</style>

效果