打造属于你的表单验证工具

在前端开发中,表单验证是一个常见且重要的需求。传统的表单验证往往导致代码重复、管理困难,尤其在面对复杂的表单时,验证逻辑容易变得混乱。为了提高开发效率和保证数据准确性,我们开发了一个高度自定义的表单验证器,能够应对各种复杂的验证场景,并支持灵活扩展新的验证规则。

一、目标与背景

表单验证是前端开发中常见的需求。很多时候,我们需要验证用户输入的数据是否符合特定规则,例如检查是否为空、是否为合法邮箱、是否符合手机号的格式等。然而,传统的表单验证往往会导致代码重复、管理困难,尤其在面对复杂的表单时,验证逻辑容易变得凌乱。

为了解决这些问题,开发一个通用的表单验证器,它具有以下特点:

  • 简洁易用:只需简单的配置即可进行表单验证。
  • 高度扩展:提供了多种常见的验证规则,并支持自定义规则。
  • 可视化反馈:能够通过添加 CSS 类自动标记出验证失败的表单项,提升用户体验。
  • 灵活处理:支持在验证时进行自定义校验逻辑,满足不同场景的需求。

二、结构和功能

1、构造函数与验证规则

表单验证器的构造函数如下:

function FormValidator(formId) {
    this.formId = formId;
    this.validationRules = {
        required: function (value) { /* 规则 */ },
        email: function (value) { /* 规则 */ },
        phone: function (value) { /* 规则 */ },
        landline: function (value) { /* 规则 */ },
        idCard: this.validateIDCard,
        number: function (value) { /* 规则 */ },
        minLength: function (value, length) { /* 规则 */ },
        maxLength: function (value, length) { /* 规则 */ },
        pattern: function (value, regex) { /* 规则 */ },
        custom: function (value, validator) { /* 规则 */ },
    };
    this.addEventListeners();
}

在此构造函数中,我们为常见的验证规则(如requiredemailphone等)定义了相应的验证函数,便于后续调用和扩展。

2、身份证号校验方法

身份证号验证是一个常见的需求,为了确保身份证号码的有效性,我们实现了如下校验逻辑:

FormValidator.prototype.validateIDCard = function (idCard) {
    if (idCard.length !== 18) return '不是有效的身份证号码!';
    if (!/^[0-9Xx]+$/.test(idCard)) return '身份证号码包含无效字符!';

    const factors = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
    const checksums = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
    let sum = 0;

    for (let i = 0; i < 17; i++) {
        sum += parseInt(idCard[i], 10) * factors[i];
    }

    return checksums[sum % 11] === idCard[17].toUpperCase() ? undefined : '身份证号码校验码不正确!';
};

此方法结合长度、字符、和校验码三个层次进行验证,确保身份证号的准确性。

3、验证每个表单项

我们通过validateField方法验证每个表单项,并根据字段类型调用相应的规则进行校验:

FormValidator.prototype.validateField = function (field) {
    const { name, type, value, minLength, maxLength, pattern, errorMsg, customValidator } = field;
    let error;

    switch (type) {
        case 'required': error = this.validationRules.required(value); break;
        case 'email': error = this.validationRules.email(value); break;
        case 'phone': error = this.validationRules.phone(value); break;
        case 'landline': error = this.validationRules.landline(value); break;
        case 'idCard': error = this.validationRules.idCard.call(this, value); break;
        case 'number': error = this.validationRules.number(value); break;
        case 'text':
            if (minLength) error = this.validationRules.minLength(value, minLength);
            if (!error && maxLength) error = this.validationRules.maxLength(value, maxLength);
            break;
        case 'pattern': if (pattern) error = this.validationRules.pattern(value, pattern); break;
        case 'custom': if (customValidator) error = this.validationRules.custom(value, customValidator); break;
    }

    return error ? { name, type, value, errorMsg: errorMsg || error } : undefined;
};

4、校验整个表单

validateForm方法负责验证整个表单,它会遍历表单数据并调用validateField进行逐项验证:

FormValidator.prototype.validateForm = function (formData = [], valiRules = [], callback) {
    const errors = [];
    const fields = this.processFormData(formData, valiRules);

    fields.forEach(field => {
        const error = this.validateField(field);
        if (error) {
            document.querySelector(`#${this.formId} [name="${error.name}"]`)?.classList.add('form-item-error');
            errors.push(error);
        }
    });

    if (callback && errors.length) callback(errors);
    return errors;
};

5、处理表单数据

processFormData方法将表单数据与验证规则结合,为每个字段应用相应的规则:

FormValidator.prototype.processFormData = function (formData, valiRules) {
    return formData.map(item => {
        const rule = valiRules.find(attr => attr.name === item.name);
        return rule ? { ...item, ...rule } : item;
    });
};

6、添加事件监听器

为了提升用户体验,表单验证器还为每个输入字段添加了focus事件监听器,用于移除错误样式:

FormValidator.prototype.addEventListeners = function () {
    const formElement = document.querySelector(`#${this.formId}`);
    formElement?.querySelectorAll('input, textarea, select').forEach(input => {
        input.addEventListener('focus', e => e.target.classList.remove('form-item-error'));
    });
};

7、完整的代码

// 表单验证构造函数
function FormValidator(formId) {
    this.formId = formId;
    // 在构造函数中初始化实例变量
    this.validationRules = {
        required: function (value) {
            return value ? undefined : '此字段为必填项';
        },
        email: function (value) {
            return /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value) ? undefined : '请输入有效的邮箱地址';
        },
        phone: function (value) {
            return /^1[3-9]\d{9}$/.test(value) ? undefined : '请输入有效的手机号码';
        },
        landline: function (value) {
            return /^(\d{3,4}-)?\d{7,8}$/.test(value) ? undefined : '请输入有效的座机号码';
        },
        idCard: this.validateIDCard, // 直接指向实例方法
        number: function (value) {
            return /^\d+$/.test(value) ? undefined : '请输入有效的数字';
        },
        minLength: function (value, length) {
            return value.length >= length ? undefined : `长度至少为${length}个字符`;
        },
        maxLength: function (value, length) {
            return value.length <= length ? undefined : `长度不得超过${length}个字符`;
        },
        pattern: function (value, regex) {
            return regex.test(value) ? undefined : '格式不正确';
        },
        custom: function (value, validator) {
            return validator(value) ? undefined : '自定义验证未通过';
        },
    };
    // 在构造函数中绑定事件监听器
    this.addEventListeners();
}

// 身份证号校验方法
FormValidator.prototype.validateIDCard = function (idCard) {
    if (idCard.length !== 18) {
        return '不是有效的身份证号码!';
    }
    const validIdCardChars = /^[0-9Xx]+$/;
    if (!validIdCardChars.test(idCard)) {
        return '身份证号码包含无效字符!';
    }
    const factors = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
    const checksums = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
    let sum = 0;
    for (let i = 0; i < 17; i++) {
        sum += parseInt(idCard[i], 10) * factors[i];
    }
    const checkDigit = checksums[sum % 11];
    if (checkDigit !== idCard[17].toUpperCase()) {
        return '身份证号码校验码不正确!';
    }
    return undefined;
};

// 校验单个字段方法
FormValidator.prototype.validateField = function (field) {
    let error = null;
    const { name, type, value, minLength, maxLength, pattern, errorMsg, customValidator } = field;

    // 根据字段类型进行校验
    switch (type) {
        case 'required':
            error = this.validationRules.required(value);
            break;
        case 'email':
            error = this.validationRules.email(value);
            break;
        case 'phone':
            error = this.validationRules.phone(value);
            break;
        case 'landline':
            error = this.validationRules.landline(value);
            break;
        case 'idCard':
            error = this.validationRules.idCard.call(this, value); // 显式使用实例方法
            break;
        case 'number':
            error = this.validationRules.number(value);
            break;
        case 'text':
            // 针对 text 类型,处理 minLength 和 maxLength 校验
            if (minLength) error = this.validationRules.minLength(value, minLength);
            if (!error && maxLength) error = this.validationRules.maxLength(value, maxLength);
            break;
        case 'pattern':
            if (pattern) error = this.validationRules.pattern(value, pattern);
            break;
        case 'custom':
            if (customValidator) error = this.validationRules.custom(value, customValidator);
            break;
        default:
            break;
    }

    // 如果校验不通过,返回 errorMsg(如果提供),否则返回默认错误信息
    const errorMessage = error ? errorMsg || error : undefined;

    // 返回带有字段信息的错误对象
    return errorMessage ? { name, type, value, errorMsg: errorMessage } : undefined;
};

// 校验整个表单方法
FormValidator.prototype.validateForm = function (formData = [], valiRules = [], callback) {
    let errors = [],
        fields = this.processFormData(formData, valiRules);

    fields.forEach(field => {
        const error = this.validateField(field);
        if (error) {
            // 添加class
            let inputElement = document.querySelector(`#${this.formId} [name="${error.name}"]`);
            if (inputElement) {
                inputElement.classList.add('form-item-error');
            }
            // 收集所有错误
            errors.push(error);
        }
    });

    if (typeof callback === 'function' && errors.length > 0) {
        callback(errors);
    }

    return errors; // 返回包含错误信息的数组
};

// 表单数据处理
FormValidator.prototype.processFormData = function (formData, valiRules) {
    // 为 formData 中对应的项添加 valiRules 中的属性
    const resultFormData = formData.map(item => {
        const attr = valiRules.find(attrItem => attrItem.name === item.name);
        if (attr) {
            return { ...item, ...attr };
        }
        return item;
    });

    return resultFormData;
};

// 为表单项添加事件监听器
FormValidator.prototype.addEventListeners = function () {
    const formElement = document.querySelector(`#${this.formId}`);

    // 为每个输入字段添加事件监听
    if (formElement) {
        const inputElements = formElement.querySelectorAll('input, textarea, select');

        inputElements.forEach(input => {
            input.addEventListener('focus', e => {
                e.target.classList.remove('form-item-error'); // 去掉错误样式
            });
        });
    }
};

8、如何使用

使用表单验证器非常简单,只需传入表单 ID,并为每个表单字段配置验证规则。以下是一个简单的使用示例:

const validator = new FormValidator('myForm');

const formData = [
    { name: 'username', value: 'mslion' },
    { name: 'phone', value: '12345678901' },
    { name: 'email', value: 'mslion@example.com' },
    { name: 'text', value: 'mslion' },
    { name: 'landline', value: '08565642157' },
    { name: 'idCard', value: '' },
    { name: 'number', value: '45621' },
    { name: 'pattern',  value: 'abc123'},
    { name: 'customField',  value: 'CustomTest' },
];

const valiRules = [
    { name: 'username', type: 'required', errorMsg: '请选填写您的姓名' },
    { name: 'phone', type: 'phone', errorMsg: '请输入电话' },
    { name: 'email', type: 'email', errorMsg: '请输入邮箱' },
    { name: 'text', type: 'text', minLength: 5, maxLength: 10, errorMsg: '文本不符合要求' },
    { name: 'landline', type: 'landline', errorMsg: '请输入座机号' },
    { name: 'idCard', type: 'idCard', errorMsg: '请输入身份证号' },
    { name: 'number', type: 'number', errorMsg: '请输入数字' },
    { name: 'pattern', type: 'pattern', pattern: /^[a-z0-9]+$/, errorMsg: '格式不正确' },
    { name: 'customField', type: 'custom', customValidator: value => value.includes('Test'), errorMsg: '自定义验证未通过' },
];

const errors = validator.validateForm(formData, valiRules, (errors) => {
    console.log(errors);
});

可添加校验不通过的 class

.form-item-error {
    color: #bb0000 !important;
    border-color: #bb0000 !important;
}

.form-item-error::placeholder {
    color: #bb0000 !important;
}

三、总结

这个表单验证器设计灵活、易于扩展,能够处理常见的验证需求,并支持自定义规则。通过集成错误反馈机制,提升了用户体验。此外,它还为表单字段提供了清晰的验证逻辑,使得开发者可以轻松维护和扩展验证规则。无论你是前端开发新手,还是资深开发者,都能通过这个工具提高开发效率和代码质量。

版权声明:
作者:码手Lion
链接:https://www.mslion.net/101/
来源:码手Lion的博客
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>