vue3 利用父子组件通信 写一个自定义表单校验组件 代码举例
时间: 2025-07-20 15:24:08 浏览: 4
<think>我们计划创建一个自定义表单校验组件,该组件将作为一个子组件,父组件通过v-model绑定值,并通过事件触发校验。
组件功能:
1. 子组件接收父组件传递的值(使用props),并通过输入框显示。
2. 当输入框的值变化时,子组件触发事件更新父组件的值(使用emit)。
3. 子组件内置校验规则,例如:非空校验、格式校验等,并在校验失败时显示错误信息。
4. 父组件可以调用子组件的方法来触发校验(使用ref)。
我们将使用Vue3的Composition API和TypeScript。
子组件:ValidateInput.vue
父组件:ParentForm.vue
步骤:
1. 子组件定义props:modelValue(用于v-model绑定)和rules(可选,传递校验规则,这里我们内置规则,所以不传递规则,而是内置)。
2. 子组件内部使用input元素,绑定modelValue,并在输入时触发update:modelValue事件。
3. 子组件定义校验方法(例如validate),并暴露给父组件(使用defineExpose)。
4. 子组件内置规则:我们这里内置两个规则:必填和邮箱格式。通过prop传入规则名(ruleName)来指定使用哪个规则,或者可以传入规则数组。但为了简单,我们直接在组件内写死规则,只做非空和邮箱格式校验。
5. 父组件通过ref获取子组件实例,并调用validate方法。
但是,根据引用[4]中提到的自定义表单组件的做法,我们可以使用自定义v-model,即通过props: modelValue和emit('update:modelValue')。
为了更通用,我们让父组件可以传递规则(rules)作为prop,但本例我们先内置规则,只做非空和邮箱校验,并通过一个prop(validator)来指定校验类型(例如:'required' | 'email')。同时,我们也可以支持多个规则,但为了简化,我们只实现一个规则。
设计:
子组件的props:
modelValue: String, // 绑定的值
validator: { // 校验规则类型,可以是字符串或数组,这里我们只支持字符串,表示一种规则
type: String,
default: ''
}
子组件内部:
- 使用input绑定值,触发input事件时,更新值并触发父组件的update:modelValue事件,同时进行实时校验(可选,也可以只在校验方法被调用时校验)。
- 定义一个错误状态(error)和错误信息(errorMessage),在校验失败时显示。
- 暴露一个validate方法,返回一个Promise或布尔值,这里我们返回Promise,以便支持异步校验(虽然本例是同步)。
校验规则:
required: 非空
email: 邮箱格式
父组件:
使用v-model绑定数据,并可以通过ref调用子组件的validate方法。
代码示例:
子组件:ValidateInput.vue</think>### Vue3 父子组件通信实现自定义表单校验组件
下面是一个完整的自定义表单校验组件实现,通过父子组件通信实现数据双向绑定和校验逻辑:
#### 子组件 ValidateInput.vue
```vue
<template>
<div class="validate-input">
<input
:type="type"
:value="modelValue"
@input="handleInput"
@blur="validate"
:class="{ 'error': error }"
:placeholder="placeholder"
/>
<div v-if="error" class="error-message">{{ errorMessage }}</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
const props = defineProps({
modelValue: {
type: String,
default: ''
},
type: {
type: String,
default: 'text'
},
placeholder: {
type: String,
default: ''
},
rules: {
type: Array as () => Array<(val: string) => boolean | string>,
default: () => []
}
})
const emit = defineEmits(['update:modelValue', 'validated'])
// 错误状态
const error = ref(false)
const errorMessage = ref('')
// 处理输入事件
const handleInput = (e: Event) => {
const value = (e.target as HTMLInputElement).value
emit('update:modelValue', value)
// 实时校验(可选)
if (error.value) validate()
}
// 校验方法(可被父组件调用)
const validate = () => {
error.value = false
errorMessage.value = ''
// 遍历所有校验规则
for (const rule of props.rules) {
const result = rule(props.modelValue)
if (result !== true) {
error.value = true
errorMessage.value = typeof result === 'string' ? result : '输入无效'
emit('validated', false)
return false
}
}
emit('validated', true)
return true
}
// 暴露校验方法给父组件
defineExpose({ validate })
</script>
<style scoped>
.validate-input {
margin-bottom: 15px;
}
input {
width: 100%;
padding: 8px 12px;
border: 1px solid #dcdfe6;
border-radius: 4px;
transition: border-color 0.3s;
}
input:focus {
outline: none;
border-color: #409eff;
}
input.error {
border-color: #f56c6c;
}
.error-message {
color: #f56c6c;
font-size: 12px;
margin-top: 4px;
}
</style>
```
#### 父组件 ParentForm.vue
```vue
<template>
<div class="form-container">
<h2>用户注册</h2>
<form @submit.prevent="handleSubmit">
<ValidateInput
v-model="formData.email"
placeholder="请输入邮箱"
type="email"
:rules="emailRules"
ref="emailInput"
/>
<ValidateInput
v-model="formData.password"
placeholder="请输入密码"
type="password"
:rules="passwordRules"
ref="passwordInput"
/>
<button type="submit" class="submit-btn">提交</button>
</form>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import ValidateInput from './ValidateInput.vue'
// 表单数据
const formData = reactive({
email: '',
password: ''
})
// 获取子组件引用
const emailInput = ref<InstanceType<typeof ValidateInput> | null>(null)
const passwordInput = ref<InstanceType<typeof ValidateInput> | null>(null)
// 邮箱校验规则
const emailRules = [
(val: string) => !!val || '邮箱不能为空',
(val: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val) || '邮箱格式不正确'
]
// 密码校验规则
const passwordRules = [
(val: string) => !!val || '密码不能为空',
(val: string) => val.length >= 6 || '密码长度至少6位',
(val: string) => /[A-Z]/.test(val) || '必须包含大写字母',
(val: string) => /\d/.test(val) || '必须包含数字'
]
// 提交表单
const handleSubmit = async () => {
// 触发所有输入框的校验
const emailValid = emailInput.value?.validate()
const passwordValid = passwordInput.value?.validate()
// 等待所有校验完成
if (emailValid && passwordValid) {
console.log('表单验证通过', formData)
// 提交逻辑...
} else {
console.log('表单验证失败')
}
}
</script>
<style scoped>
.form-container {
max-width: 400px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ebeef5;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.submit-btn {
width: 100%;
padding: 10px 15px;
background-color: #409eff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.submit-btn:hover {
background-color: #66b1ff;
}
</style>
```
### 关键实现说明
1. **父子组件通信**:
- 父组件通过 `v-model` 实现数据双向绑定[^2]
- 子组件通过 `defineProps` 接收父组件数据
- 子组件通过 `defineEmits` 向父组件发送事件
- 父组件通过 `ref` 获取子组件实例并调用其方法[^4]
2. **自定义校验规则**:
- 通过 `rules` prop 传递校验函数数组
- 每个校验函数返回 `true`(通过)或错误信息(失败)
- 支持多种校验规则组合(必填、格式、长度等)
3. **校验触发机制**:
- 实时校验(输入时)
- 失焦校验(`@blur` 事件)
- 手动触发(父组件调用 `validate()` 方法)
4. **错误反馈**:
- 输入框边框变红
- 显示具体错误信息
- 通过事件通知父组件校验结果
### 最佳实践建议
1. **规则复用**:创建 `validationRules.ts` 文件统一管理校验规则
```typescript
// validationRules.ts
export const required = (msg: string) => (val: string) => !!val || msg
export const email = (msg: string) => (val: string) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val) || msg
export const minLength = (len: number, msg: string) => (val: string) =>
val.length >= len || msg
```
2. **异步校验**:扩展校验函数支持 Promise
```typescript
// 子组件校验方法修改
const validate = async () => {
for (const rule of props.rules) {
const result = await rule(props.modelValue)
if (result !== true) {
// 错误处理...
}
}
}
```
3. **表单级校验**:在父组件实现跨字段校验逻辑
```typescript
// 父组件增加确认密码校验
const confirmPasswordRules = [
(val: string) => val === formData.password || '两次输入密码不一致'
]
```
阅读全文
相关推荐













