Vue3开发极简入门(15.1):emits补完-结合v-model
之前代码是通过按钮触发emit,如果希望输入框里的内容在输入之后也能同步到父组件,就可以结合v-model的update事件来操作,具体如下。
Son2.vue:
<template>
<div class='son2'>
<h1>子组件2</h1>
上报司机:{{ driverName }}<br />
<input type="text" :value="driverName" @input="handleInput($event)" placeholder="输入司机姓名" />
</div>
</template>
<script lang='ts' setup name='Son2'>
import { ref } from 'vue';
const driverName = ref('')
const props = defineProps(['driverName']);
const emit = defineEmits(['update:driverName']);
const handleInput = (event: Event) => {
const target = event.target as HTMLInputElement;
driverName.value = target.value
emit('update:driverName', target.value);
};
</script>
<style scoped>
.son2 {
background-color: rgb(85, 193, 236);
padding: 10px;
margin-top: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
width: 90%;
margin: auto;
}
</style>
父组件修改:
<template>
<div class="father">
<h1>父组件</h1>
司机:{{ driverName }}<br />
装货地:北京,卸货地:海口,发车时间:{{ departTime }}
<Son @set-depart-time="setDepartTime" />
<!--如果子组件传多个属性,可以直接在后面加,例如 v-model:dirverId="driverId"-->
<Son2 v-model:driverName="driverName" />
</div>
</template>
<script lang='ts' setup name='Father'>
import { ref } from 'vue';
import Son from './Son.vue';
import Son2 from './Son2.vue';
const departTime = ref('待定')
const driverName = ref('无')
function setDepartTime(val: string) {
departTime.value = val
}
</script>
<style scoped>
.father {
background-color: darkseagreen;
box-shadow: 0 0 10px black;
border-radius: 10px;
height: 100%;
}
</style>
整个流程,就是子组件的输入框绑定了输入回调函数handleInput,然后触发update事件,而父组件的v-model会监听此事件,并自动更新绑定的数据。子组件需要通过defineProps来接收父组件传递的值。
如果大家搜网上的资料,代码中的driverName大多是写成modelValue。modelValue适用1个属性,如果传递多个属性给父组件,例如司机姓名、司机身份证号等,就可以用文中的写法。
高版本的(3.4+),有比较简单的写法,就是defineModel语法糖,其本质就是自动生成defineProps、defineEmits。
Son2.vue修改如下:
<template>
<div class='son2'>
<h1>子组件2</h1>
<label>
司机ID:
<input type="number" v-model="driverId">
</label>
<label>
司机姓名:
<input v-model="driverName">
</label>
</div>
</template>
<script lang='ts' setup name='Son2'>
import { ref } from 'vue';
const driverId = defineModel('driverId', {
type: Number, default: 0, required: true, set(value: number) { // 数字输入校验
return isNaN(value) ? 0 : Number(value);
}
})
const driverName = defineModel('driverName')
</script>
<style scoped>
.son2 {
background-color: rgb(85, 193, 236);
padding: 10px;
margin-top: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
width: 90%;
margin: auto;
}
</style>
父组件修改:
<template>
<div class="father">
<h1>父组件</h1>
司机:{{ driverName }}<br />
司机ID:{{ driverId }}<br />
装货地:北京,卸货地:海口,发车时间:{{ departTime }}
<Son @set-depart-time="setDepartTime" />
<Son2 v-model:driverName="driverName" v-model:driver-id="driverId" />
</div>
</template>
<script lang='ts' setup name='Father'>
import { ref } from 'vue';
import Son from './Son.vue';
import Son2 from './Son2.vue';
const departTime = ref('待定')
const driverName = ref('无')
const driverId = ref(0)
function setDepartTime(val: string) {
departTime.value = val
}
</script>
<style scoped>
.father {
background-color: darkseagreen;
box-shadow: 0 0 10px black;
border-radius: 10px;
height: 100%;
}
</style>
请注意看看父子组件driverId和driverName写法的不同,我是故意这么写的:
- 子组件定义driverId的写法,比较全,包含了类型定义、默认值、是否必填、校验&转换。
- 父组件关于driverId的写法,“driverId”是父组件自己的,而driver-id是子组件defineModel('driverId')的那个dirverId。这一点我忘了在props一节中说明:可以使用驼峰形式(driverName用的就是),但是Vue官网上说为了和HTML attribute对齐,通常会将其写为kebab-case(短横线)形式。实际工作中,看公司自己的规范写吧。
- TypeScript对类型要求严格,可以写作const driverName = defineModel<string>('driverName')。