二次元绘画创作
56.21M · 2026-02-04
Vue3中组件的双向绑定本质是props与emit的语法糖。在Vue3.4+版本,官方推荐使用defineModel()宏简化实现,而低版本则需要手动处理属性与事件的传递。
<!-- CustomInput.vue -->
<script setup>
// defineModel自动处理props和emit的双向绑定
const model = defineModel()
</script>
<template>
<input
v-model="model"
placeholder="请输入内容"
class="custom-input"
/>
</template>
<style scoped>
.custom-input {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
</style>
父组件使用:
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'
const inputValue = ref('')
</script>
<template>
<div>
<CustomInput v-model="inputValue" />
<p class="mt-2">输入结果:{{ inputValue }}</p>
</div>
</template>
<!-- CustomInputLegacy.vue -->
<script setup>
// 接收父组件传递的value
const props = defineProps(['modelValue'])
// 定义更新事件
const emit = defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="props.modelValue"
@input="emit('update:modelValue', $event.target.value)"
placeholder="请输入内容"
class="custom-input"
/>
</template>
父组件使用方式与defineModel版本完全一致。
<!-- CustomSelect.vue -->
<script setup>
const model = defineModel()
// 接收选项配置
const props = defineProps({
options: {
type: Array,
required: true,
default: () => []
},
placeholder: {
type: String,
default: '请选择'
}
})
</script>
<template>
<select v-model="model" class="custom-select">
<option value="" disabled>{{ props.placeholder }}</option>
<option
v-for="option in props.options"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</option>
</select>
</template>
<style scoped>
.custom-select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
background-color: white;
}
</style>
父组件使用:
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import CustomSelect from './CustomSelect.vue'
const selectedValue = ref('')
const selectOptions = [
{ value: 'vue', label: 'Vue.js' },
{ value: 'react', label: 'React' },
{ value: 'angular', label: 'Angular' }
]
</script>
<template>
<div>
<CustomSelect
v-model="selectedValue"
:options="selectOptions"
placeholder="选择前端框架"
/>
<p class="mt-2">选中值:{{ selectedValue }}</p>
</div>
</template>
Vue3支持在单个组件上绑定多个v-model,通过指定参数区分:
<!-- UserForm.vue -->
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>
<template>
<div class="flex gap-2">
<input v-model="firstName" placeholder="姓" class="custom-input" />
<input v-model="lastName" placeholder="名" class="custom-input" />
</div>
</template>
父组件使用:
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import UserForm from './UserForm.vue'
const userFirstName = ref('')
const userLastName = ref('')
</script>
<template>
<div>
<UserForm
v-model:first-name="userFirstName"
v-model:last-name="userLastName"
/>
<p class="mt-2">姓名:{{ userFirstName }} {{ userLastName }}</p>
</div>
</template>
自定义组件也可以支持v-model修饰符,比如实现首字母大写:
<!-- CustomInputWithModifier.vue -->
<script setup>
const [model, modifiers] = defineModel({
set(value) {
// 处理capitalize修饰符
if (modifiers.capitalize && value) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
return value
}
})
</script>
<template>
<input v-model="model" placeholder="请输入内容" class="custom-input" />
</template>
父组件使用:
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import CustomInputWithModifier from './CustomInputWithModifier.vue'
const inputValue = ref('')
</script>
<template>
<div>
<CustomInputWithModifier v-model.capitalize="inputValue" />
<p class="mt-2">处理后的值:{{ inputValue }}</p>
</div>
</template>
封装一个集成验证逻辑的输入框组件,支持多种验证规则:
<!-- ValidatedInput.vue -->
<script setup>
import { ref, computed } from 'vue'
const model = defineModel()
const props = defineProps({
rules: {
type: Object,
default: () => ({})
},
label: {
type: String,
default: ''
}
})
const showError = ref(false)
const errorMessage = ref('')
// 验证输入值
const validate = (value) => {
showError.value = false
errorMessage.value = ''
// 必填验证
if (props.rules.required && !value) {
showError.value = true
errorMessage.value = props.rules.requiredMessage || '此字段为必填项'
return false
}
// 最小长度验证
if (props.rules.minLength && value.length < props.rules.minLength) {
showError.value = true
errorMessage.value = props.rules.minLengthMessage ||
`最少需要输入${props.rules.minLength}个字符`
return false
}
// 邮箱格式验证
if (props.rules.email && value) {
const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/
if (!emailRegex.test(value)) {
showError.value = true
errorMessage.value = props.rules.emailMessage || '请输入有效的邮箱地址'
return false
}
}
return true
}
// 失去焦点时触发验证
const handleBlur = () => {
validate(model.value)
}
// 输入时清除错误提示
const handleInput = () => {
showError.value = false
errorMessage.value = ''
}
</script>
<template>
<div class="validated-input">
<label v-if="props.label" class="input-label">{{ props.label }}</label>
<input
v-model="model"
@blur="handleBlur"
@input="handleInput"
:class="{ 'input-error': showError }"
class="custom-input"
:placeholder="props.label || '请输入内容'"
/>
<div v-if="showError" class="error-message">{{ errorMessage }}</div>
</div>
</template>
<style scoped>
.validated-input {
margin-bottom: 16px;
}
.input-label {
display: block;
margin-bottom: 4px;
font-size: 14px;
font-weight: 500;
}
.input-error {
border-color: #ff4d4f;
}
.error-message {
margin-top: 4px;
font-size: 12px;
color: #ff4d4f;
}
</style>
父组件使用:
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import ValidatedInput from './ValidatedInput.vue'
const email = ref('')
const emailRules = {
required: true,
requiredMessage: '邮箱不能为空',
email: true,
emailMessage: '请输入有效的邮箱地址'
}
</script>
<template>
<ValidatedInput
v-model="email"
label="邮箱地址"
:rules="emailRules"
/>
</template>
封装一个支持格式化和范围选择的日期选择器:
<!-- DatePicker.vue -->
<script setup>
import { ref, computed } from 'vue'
const model = defineModel()
const props = defineProps({
format: {
type: String,
default: 'YYYY-MM-DD'
},
placeholder: {
type: String,
default: '选择日期'
}
})
// 格式化显示的日期
const formattedDate = computed(() => {
if (!model.value) return ''
const date = new Date(model.value)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
})
// 处理日期变化
const handleDateChange = (e) => {
model.value = e.target.value
}
</script>
<template>
<div class="date-picker">
<input
type="date"
:value="formattedDate"
@change="handleDateChange"
:placeholder="props.placeholder"
class="custom-input"
/>
<p v-if="model.value" class="mt-2">选中日期:{{ formattedDate }}</p>
</div>
</template>
父组件使用:
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import DatePicker from './DatePicker.vue'
const selectedDate = ref('')
</script>
<template>
<DatePicker v-model="selectedDate" />
</template>
placeholder、disabled、size等通过插槽增强组件的扩展性:
<!-- CustomInputWithSlot.vue -->
<script setup>
const model = defineModel()
</script>
<template>
<div class="input-group">
<slot name="prefix"></slot>
<input v-model="model" class="custom-input" />
<slot name="suffix"></slot>
</div>
</template>
父组件使用插槽:
<CustomInputWithSlot v-model="value">
<template #prefix>
<span class="prefix-icon"></span>
</template>
<template #suffix>
<button @click="clearInput">清除</button>
</template>
</CustomInputWithSlot>
:root {
--input-border-color: #ddd;
--input-focus-color: #409eff;
--input-error-color: #ff4d4f;
}
class props传递自定义样式类v-bind="$attrs"透传原生事件validate-success、validate-failkebab-case命名,如update:model-valueprovide和inject实现跨组件通信,如表单验证状态的共享答案解析:
defineModel()宏:<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model" />
</template>
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="props.modelValue"
@input="emit('update:modelValue', $event.target.value)"
/>
</template>
父组件统一使用v-model="value"绑定。
答案解析:
通过为defineModel()指定参数实现多v-model绑定:
<!-- 子组件 -->
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>
<template>
<input v-model="firstName" placeholder="姓" />
<input v-model="lastName" placeholder="名" />
</template>
父组件使用:
<CustomComponent
v-model:first-name="userFirstName"
v-model:last-name="userLastName"
/>
答案解析:
产生原因:自定义组件使用了v-model,但父组件未绑定值,或子组件未正确定义props。 解决办法:
v-model="value"绑定响应式变量defineModel()或声明modelValue prop产生原因:v-model绑定的变量类型与子组件期望的prop类型不匹配。 解决办法:
.number修饰符或在defineModel()中指定类型产生原因:子组件未声明update:modelValue事件,或使用了片段根节点导致事件无法自动继承。
解决办法:
defineModel()宏自动处理事件声明defineEmits(['update:modelValue'])声明事件