Поэтапная элегантная проверка формы

JavaScript
Поэтапная элегантная проверка формы

источник

В последнее время меня долго задерживает большой спрос на продукцию Жасмин.Давно не писала,и немного напугана,поэтому сим устраиваюсь снять тревогу 😂. Сегодня в основном поделюсь знаниями о верификации форм.Все должны знать, что это верификация имени пользователя, адреса электронной почты, номера мобильного телефона и т.д.
Вообще говоря, проверку формы можно разделить на два типа: мгновенная проверка (локальная проверка) и асинхронная проверка (например, доступно ли имя пользователя, проверочный код и т. д.), которую можно понимать как предварительную проверку и проверку обратно. end Verification (front-end и back-end на работе).Все они должны быть проверены, чтобы гарантировать точность и достоверность конечных данных.Я считаю, что у всех также должна быть проверка 😁), и то, что мы в основном объясняем сегодня, это фронтальная проверка формы.

Цель

👌, прежде всего, коротко о целевой функции, которую необходимо достичь:

  • Имеет базовую функциональность проверки формы
  • Предоставляет некоторые встроенные правила проверки
  • Дают возможность открываться внешнему миру

На самом деле валидация формы может существовать независимо от страницы, по сути это функция, которая принимает два параметра (данные и правила), а затем выполняет верификацию и, если в верификации возникает ошибка, возвращает соответствующее сообщение об ошибке. Смысл должен быть понятен и написан всем, а как его написать изящно, или сделать его более удобным в разработке и использовании, давайте посмотрим от 0 до 1🧐.

первое издание

Итак, начало всего сложно🤨, с чего же начать? Очевидно, наше мышление состоит из двух шагов:

  1. Сначала получите значения и правила для проверки;
  2. Затем выполните соответствующую проверку правила и верните результат проверки.

В частности, нам нужно написать функцию и передать два параметра (данные и правила), и она также должна возвращать объект ошибки, например следующий👇:

function validate(data, rules) {
    // ...
}
// 数据大概长这样
let data = {
    name: 'xxx',
    phone: '138xxxxxxxx'
}
// 规则大概长这样,为什么长这样,你用过一些 UI 框架应该会有点感觉
let rules = [{
    key: 'name',
    required: true,
    minLen: 6,
    maxLen: 10
}, {
    key: 'phone',
    pattern: 'phone'
}]
// 错误信息大概长这样
let errors = {
    name: {
        required: '必填',
        minLen: '太短了',
        maxLen: '太长了'
    },
    phone: {
        pattern: '手机格式不对'
    }
}

Приведенный выше, казалось бы, простой код на самом деле является скрытой загадкой.Здесь я в основном подчеркиваю следующие два-три момента:

  • Правила rules представляют собой массив.Почему, ведь нам часто нужно проверять по порядку в реальной работе, поэтому чтобы записать это в виде массива, мы должны проверять соответствующие данные согласно порядку правил.
  • Каждые данные могут возвращать несколько сообщений об ошибках. Мы отображаем только одно из них или все? Об этом может подумать каждый 🤔. . . хорошо, ответ раскрыт, обычно нам нужно только записать ошибку, потому что обычно на странице отображается только одно сообщение об ошибке, то есть, если определенные данные неверны, не проверяйте другие ошибки данных, там в этом нет необходимости, но эта статья покажет все ошибки 😯, ха-ха.
  • Кроме того, в каждом правилеrequiredПриоритет поля всегда наивысший.Оно особое по сравнению с другими правилами.Ведь значения нет.Что толку в других правилах.

Далее нам нужно только улучшить функцию проверки.Общая идея состоит в том, чтобы зациклить правила, получить соответствующее значение данных для проверки и записать его, если есть ошибка.errorsВнутри это выглядит так👇:

function validate(data, rules) {
    let errors = {};  // 有错误的话放这里面
    rules.forEach(rule => {
        let val = data[rule.key]
        if (rule.required) {
            if (!val && val !== 0) {
                setDefaultObj(errors, rule.key) // 这个函数在下面,目的是为了确保 errors[rule.key] 是个对象
                errors[rule.key].required = '必填'
                return // 如果没填就直接 return 了,不需要再进行此数据的其他校验
            }
        }
        if (rule.pattern) {
            if (rule.pattern === 'phone') {
                if(!/^1\d{10}$/.test(val)) { // 简单校验了一下手机
                    setDefaultObj(errors, rule.key)
                    errors[rule.key].pattern = '手机格式错误'
                }
            }
        }
        if (rule.minLen) {
            if (val.length < rule.minLen) {
                setDefaultObj(errors, rule.key)
                errors[rule.key].minLen = '太短啦'
            }
        }
        if (rule.maxLen) {
            if (val.length > rule.maxLen) {
                setDefaultObj(errors, rule.key)
                errors[rule.key].maxLen = '太长啦'
            }
        }
        console.log(errors)
    });
}
function setDefaultObj(obj, key) { // 确保是个对象,以便于赋值
    obj[key] = obj[key] || {}
}

Давайте выполним вышеуказанную функцию с узлом и увидим следующий результат:Все верно, выше это весь код нашей первой редакции, которая была написана 😎, его легко понять, если контента не так много.
Но этого далеко не достаточно.Хотя основные функции реализованы, недостатки тоже крайне очевидны:

  • Если будет еще несколько проверок, насколько толстой станет эта функция?
  • Слишком много if-else означает, что нам нужно сделать его элегантным
  • нет возможности повторного использования
  • Есть некоторая, казалось бы, повторяющаяся логика
  • Если мы хотим изменить правило, мы должны изменить его в функции, что нарушает принцип открытого-закрытого

Так давайте изменим, вы измените (маленькое, вы можете изменить свою любовь, изменить свое тело), ​​конечно, вы еще можете подумать об этом. . .

второе издание

Первое, что мы можем придумать, это убрать if-else и извлечь наружу логику проверки. Мы все знаем, что функция на самом деле является объектом, поэтому мы можем написать метод проверки непосредственно в свойствах функции, напримерfn.required = () => {}илиfn.pattern = () => {}Выглядит это так, вот конкретный код после модификации👇:

function validate(data, rules) {
    let errors = {};  // 有错误的话放这里面
    rules.forEach(rule => {
        let val = data[rule.key]
        if (rule.required) {
            let error = validate.required(val)
            if (error) {
                setDefaultObj(errors, rule.key)
                errors[rule.key] = error
                return
            }
        }
        if (rule.pattern) {
            let error = validate.pattern(val, rule.pattern)
            if (error) {
                setDefaultObj(errors, rule.key)
                errors[rule.key].pattern = error
            }
        }
        if (rule.minLen) {
            let error = validate.minLen(val, rule.minLen)
            if (error) {
                setDefaultObj(errors, rule.key)
                errors[rule.key].minLen = error
            }
        }
        if (rule.maxLen) {
            let error = validate.maxLen(val, rule.maxLen)
            if (error) {
                setDefaultObj(errors, rule.key)
                errors[rule.key].maxLen = error
            }
        }
        console.log(errors)
    });
}
validate.required = (val) => {
    if (!val && val !== 0) {
        return '必填'
    }
}
validate.pattern = (val, pattern) => { // pattern 可以是用户自定义的正则也可以是内置的
    if (pattern === 'phone') {
        if(!/^1\d{10}$/.test(val)) {
            return '手机格式错误'
        }
    } else if(!pattern.test(val)) {
        return '手机格式错误'
    }
}
validate.minLen = (val, minLen) => {
    if (val.length < minLen) {
        return '太短啦'
    }
}
validate.maxLen = (val, maxLen) => {
    if (val.length > maxLen) {
        return '太长啦'
    }
}

После его изменения вы можете соврать 🤐, количество кода вроде бы не уменьшилось, а повторы еще более очевидны, что довольно неэлегантно. Корыто верное, но по сравнению с первой версией видно, что мы извлекли правила, по крайней мере не все они запихнуты в функцию валидации, можно сконцентрироваться на модификации соответствующих правил вне функции, а также Доп. правила могут быть добавлены вне функции.
Но этого недостаточно.Те недостатки, о которых я только что упомянул, кажется, остались, особенно следующий абзац очень повторяется.Вы можете видеть, что каждый if-else пишется почти одинаково, за исключением одного слова, а это значит, что мы можем продолжайте переписывать его. Как переписать, можно подумать еще раз🤔. . .

if (rule.required) {}
if (rule.pattern) {}
if (rule.minLen) {}
if (rule.maxLen) {}

Третье издание

Очень простая идея состоит в том, чтобы обойти его, но мы должны обратить внимание на внутреннюю часть каждого правила.key: 'xxx'а такжеrequired: trueявляются особыми, нам нужно их исключить и пройти по другим правилам, а другие правила можно считать равными. В частности, посмотрите на следующий код👇, вы должны быть в состоянии понять с комментариями🙄:

function validate(data, rules) {
    let errors = {};  // 有错误的话放这里面
    rules.forEach(rule => {
        let val = data[rule.key]
        if (rule.required) { // required 比较特殊,单独处理比较合适
            let error = validate.required(val)
            if (error) {
                setDefaultObj(errors, rule.key)
                errors[rule.key] = error
                return
            }
        }
        let restKeys = Object.keys(rule).filter(key => key !== 'key' && key !== 'required'); // 过滤掉 key 和 required
        restKeys.forEach(restKey => {
            if (validate[restKey]) { // 这里要注意规则可能不存在,这时候需要给用户一个警告或者报错
                let error = validate[restKey](val, rule[restKey])
                if (error) {
                    setDefaultObj(errors, rule.key)
                    errors[rule.key][restKey] = error
                }
            } else {
                throw `${restKey} 规则不存在`
            }
        })
    });
    console.log(errors)
    return errors
}

Ха-ха😊, похоже, сейчас хочется поудобнее, но все равно немного неуклюже, да и универсальность и масштабируемость кажутся недостаточно сильными. Если кто-то изменит этот материал, повлияет ли это на других людей? Или правила будут противоречить друг другу? Значит, проблема все еще существует.
На самом деле наша текущая верификация публичная, и нам нужно разделить правила на два типа, одни публичные, а другие кастомные (которые могут охватывать публичные и не затрагивать других). Что вы имеете в виду, на самом деле он переписан с цепочкой прототипов и наследованием 😁, и потому что теперь с ES6 нам не нужноprototypeписать, но использоватьclass, по сути они одинаковые, синтаксический сахар, вкусно и удобно 😋.

Четвертое издание

хорошо, теперь воспользуемсяclassПереписать приведенную выше функцию проверки (не понимаюclassПредложение метода написания - сначала посмотреть на него, это очень просто, просто измените метод написания, просто привыкните к нему), вот непосредственно код👇:

class Validator {
    constructor() {
    }
    static addRule (name, fn) { // 全局添加新规则
        Validator.prototype[name] = fn
    }
    validate(data, rules) {
        let errors = {}
        rules.forEach(rule => {
            let val = data[rule.key]
            if (rule.required) {
                let error = this.required(val)
                if (error) {
                    this.setDefaultObj(errors, rule.key)
                    errors[rule.key] = error
                    return
                }
            }
            let restKeys = Object.keys(rule).filter(key => key !== 'key' && key !== 'required');
            restKeys.forEach(restKey => {
                if (this[restKey]) {
                    let error = this[restKey](val, rule[restKey])
                    if (error) {
                        this.setDefaultObj(errors, rule.key)
                        errors[rule.key][restKey] = error
                    }
                } else {
                    throw `${restKey} 规则不存在`
                }
            })
        });
        console.log(errors)
    }
    required (val) {
        if (!val && val !== 0) {
            return '必填'
        }
    }
    pattern (val, pattern) { // pattern 可以是用户自定义的正则也可以是内置的
        if (pattern === 'phone') {
            if(!/^1\d{10}$/.test(val)) {
                return '手机格式错误'
            }
        } else if(!pattern.test(val)) {
            return '手机格式错误'
        }
    }
    minLen (val, minLen) {
        if (val.length < minLen) {
            return '太短啦'
        }
    }
    maxLen (val, maxLen) {
        if (val.length > maxLen) {
            return '太长啦'
        }
    }
    setDefaultObj (obj, key) {
        obj[key] = obj[key] || {}
    }
}
// 当然,使用方法也得跟着变,但是打印的错误信息是一样的
let validator = new Validator()
validator.validate(data, rules)

Разве это не немного похоже на то, когда видишь луну, когда видишь лунный свет 🤯? Нет проблем, в любом случае, описанный выше метод написания должен быть небольшим шагом по сравнению с оригинальной первой версией, а также его легко расширять и поддерживать, что очень хорошо 👏👏👏. Конечно, вы также можете просмотреть четвертое издание за десять лет и добавить или удалить его пять раз, чтобы сделать его более полным и элегантным.

будить

Вернуться к фактической работе, мы часто не хотим менять его после написания, если иначе, что должно быть немного неловко 😂, я тоже. Но в слове, если вы хотите элегантно написать, вы должны написать больше и сделать больше изменений. Лучше, чем оригинал - это прогресс. Это постепенный процесс, а не одноэтапный процесс. Наконец, я надеюсь, что эта статья может быть полезной для всех, и похвала безграничная 👍👍👍. . .
ps: После написания статьи продукт неожиданно дал мне краткое введение в следующее требование.После того, как я его услышал, я долго писал.