实战案例:构建一个可复用的表单验证 Hook
在 Vue 3 开发中,表单验证是常见需求,通常涉及输入校验、错误提示和提交处理。借助 Composition API 的自定义 Hook,我们可以封装一个通用的表单验证逻辑,使其在多个组件中复用。本节将通过实战案例,设计并实现一个可复用的表单验证 Hook,展示如何结合响应式 API 和生命周期钩子解决实际问题。
需求分析
我们需要一个表单验证 Hook,具备以下功能:
- 支持多字段:管理多个输入字段的状态和验证规则。
- 动态校验:实时验证输入并显示错误。
- 提交处理:提供表单提交方法,验证通过后执行操作。
- 可配置:允许自定义规则和错误消息。
- 复用性:适用于不同表单场景。
设计与实现
Hook 定义
创建一个 useFormValidation Hook,放在 src/composables/useFormValidation.js:
import { reactive, ref, watch } from 'vue';
export function useFormValidation(initialFields, rules) {
// 表单字段状态
const form = reactive({ ...initialFields });
// 错误信息
const errors = reactive({});
// 是否验证通过
const isValid = ref(false);
// 验证单个字段
const validateField = (field) => {
const value = form[field];
const fieldRules = rules[field] || [];
// 清空该字段的错误
errors[field] = '';
// 执行所有规则
for (const rule of fieldRules) {
const result = rule.validator(value);
if (result !== true) {
errors[field] = rule.message;
return;
}
}
};
// 验证整个表单
const validateForm = () => {
Object.keys(form).forEach(field => validateField(field));
isValid.value = Object.values(errors).every(error => !error);
return isValid.value;
};
// 实时监听字段变化
Object.keys(form).forEach(field => {
watch(() => form[field], () => {
validateField(field);
validateForm(); // 更新整体状态
});
});
// 重置表单
const resetForm = () => {
Object.assign(form, initialFields);
Object.keys(errors).forEach(field => (errors[field] = ''));
isValid.value = false;
};
// 提交处理
const submit = (onSuccess) => {
if (validateForm()) {
onSuccess(form);
}
};
// 初始验证
validateForm();
return {
form,
errors,
isValid,
validateField,
validateForm,
resetForm,
submit
};
}
参数说明
initialFields:初始表单字段对象(如{ name: '', email: '' })。rules:验证规则对象,格式为:{ field: [ { validator: value => condition, message: '错误消息' } ] }
使用示例
简单登录表单
<template>
<div>
<h2>登录</h2>
<div>
<label>用户名</label>
<input v-model="form.name" />
<span v-if="errors.name" class="error">{{ errors.name }}</span>
</div>
<div>
<label>密码</label>
<input v-model="form.password" type="password" />
<span v-if="errors.password" class="error">{{ errors.password }}</span>
</div>
<button :disabled="!isValid" @click="submit(handleSubmit)">提交</button>
<button @click="resetForm">重置</button>
</div>
</template>
<script>
import { useFormValidation } from '@/composables/useFormValidation';
export default {
setup() {
const initialFields = {
name: '',
password: ''
};
const rules = {
name: [
{ validator: value => value.length > 0, message: '用户名不能为空' },
{ validator: value => value.length >= 3, message: '用户名至少3个字符' }
],
password: [
{ validator: value => value.length > 0, message: '密码不能为空' },
{ validator: value => value.length >= 6, message: '密码至少6个字符' }
]
};
const {
form,
errors,
isValid,
resetForm,
submit
} = useFormValidation(initialFields, rules);
const handleSubmit = (formData) => {
console.log('提交成功:', formData);
};
return { form, errors, isValid, resetForm, submit };
}
};
</script>
<style scoped>
.error {
color: red;
font-size: 12px;
}
div {
margin: 10px 0;
}
input {
display: block;
margin: 5px 0;
}
</style>
- 效果:
- 输入时实时验证,显示错误消息。
- 所有字段有效时,提交按钮启用,点击打印表单数据。
- 重置按钮恢复初始状态。
高级用法
自定义复杂规则
支持正则表达式或异步验证:
const rules = {
email: [
{ validator: value => value.length > 0, message: '邮箱不能为空' },
{ validator: value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), message: '邮箱格式错误' }
],
username: [
{
validator: async (value) => {
const response = await fetch(`/api/check-username?value=${value}`);
return response.ok;
},
message: '用户名已存在'
}
]
};
- 注意:异步验证需配合
async/await,可能需要额外的加载状态管理。
与生命周期结合
在组件挂载时预填数据:
<script>
import { useFormValidation } from '@/composables/useFormValidation';
import { onMounted } from 'vue';
export default {
setup() {
const initialFields = { name: '', email: '' };
const rules = { /* ... */ };
const { form, errors, isValid } = useFormValidation(initialFields, rules);
onMounted(async () => {
const response = await fetch('/api/user');
const user = await response.json();
Object.assign(form, user); // 预填数据
});
return { form, errors, isValid };
}
};
</script>
设计亮点
- 响应式管理:
- 使用
reactive管理表单和错误状态,确保实时更新。
- 使用
- 灵活性:
- 支持任意字段和规则,适应不同表单需求。
- 状态完整:
- 提供
isValid和errors,便于控制 UI。
- 提供
- 复用性:
- 可在登录、注册或其他表单场景直接使用。
注意事项
- 性能优化:
- 避免过于复杂的规则或频繁触发的
watch,可添加防抖:import { debounce } from 'lodash'; watch(() => form[field], debounce(() => validateField(field), 300));
- 避免过于复杂的规则或频繁触发的
- 规则顺序:
- 规则按数组顺序执行,优先级高的放在前面。
- 错误清理:
- 确保
errors对象的键与字段一致,避免冗余。
- 确保
扩展:类型支持
为 Hook 添加 TypeScript 类型:
interface FormField {
[key: string]: string | number | boolean;
}
interface ValidationRule {
validator: (value: any) => boolean | Promise<boolean>;
message: string;
}
export function useFormValidation<T extends FormField>(
initialFields: T,
rules: { [K in keyof T]?: ValidationRule[] }
) {
const form = reactive<T>({ ...initialFields });
const errors = reactive<Partial<Record<keyof T, string>>>({});
// ... 其余代码 ...
}
使用时:
const initialFields = { name: '', email: '' };
const rules = {
name: [{ validator: v => v.length > 0, message: '必填' }],
email: [{ validator: v => v.includes('@'), message: '格式错误' }]
};
const { form, errors } = useFormValidation(initialFields, rules);
总结
通过 useFormValidation Hook,我们实现了一个功能强大且可复用的表单验证方案。它结合 reactive、watch 和自定义逻辑,满足实时校验和提交需求。掌握此案例后,你可以根据项目需要扩展规则或功能,轻松应对各种表单场景。本章结束,你已深入 Composition API 的高级应用,下一章将探讨组件化开发的更多技巧!
