目录
组件数据的双向绑定
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”,在这个代码中,prop是modelValue,因此事件名为“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>
效果: