Исходя из опыта работы с Vue.js, переход на React был для меня довольно неприятным. Одной из многих вещей, которые мне было намного сложнее реализовать (и под реализацией я подразумеваю сделать это правильно), была проверка формы и привязка данных. Если вы знакомы с Vue, вы знаете, что у него есть волшебная штука под названием v-model. Вы также можете создавать собственные привязки v-модели и создавать компоненты с самопроверкой. Если вы не знакомы с Vue, но все еще кодируете React, что угодно. Я обещаю, что это не статья о Vue.js. Я попробую.
Для меня после работы часы часто были похожи - гуглил «Как проверять формы React», «Как использовать хуки для проверки форм» и т. Д. Я действительно хотел найти или изобрести способ, который сэкономит время и даст хорошие результаты. Я был мотивирован созданием пользовательских интерфейсов с множеством форм на моем рабочем месте, поэтому я хотел тратить меньше времени на реализацию привязки данных и проверку формы, потому что это потребовало много времени и энергии.
Я продолжал создавать множество настраиваемых форм и компонентов ввода и всегда доходил до точки, когда мне приходилось писать гигантские конфигурации для самособирающихся форм. Они действительно работали, но не предложили мне много вариантов конфигурации макета - например, если бы я хотел использовать сетку для элементов, мне потребовалось бы написать дополнительную конфигурацию и заставить компоненты реагировать на нее. Я не могу представить, чтобы с этим работали мои коллеги, да и не хотел бы. Я хотел найти менее громоздкий способ проверки моих форм.
Крючки.
Это слово, да. Крючки.
Меня вдохновил хук ввода Дэна Абрамова, который, должно быть, выглядел примерно так (извинения, пишу из головы):
export function useInput(initialValue = '') { const [value, setValue] = useState(initialValue); const onChange = e => setValue(e.target.value); return { onChange, value } }
Это начало. Это ловушка, которую вы будете вызывать для ввода, например,
<input type="text" {...useInput('initValue')} />
И он будет управлять своим значением - то, что мы получаем от ловушки, - это функция onChange и входное значение, поэтому допустим, что он обновится сам. Вы можете добавить туда некоторую обработку ошибок (привязать класс ошибки - например, is-invalid, если вы используете загрузочный CSS), но это почти все. У вас по-прежнему нет двусторонней связанной формы, у вас просто есть своего рода самоконтролируемый ввод. Но это только начало.
Итак, что я создал?
Я создал файл с перехватчиками и проверочными функциями, который может быть не очень приятным для просмотра, но он хорошо подходит для моих нужд. Я все еще занимаюсь рефакторингом. Плюс в том, что мне пришлось закодировать его только один раз, и я буду использовать его для проверки своих форм, пока не найду / не создам что-то получше. Он используется в компоненте следующим образом:
Это компонент с двумя полями (Material UI) и одной кнопкой. Кнопка неактивна, если поля недействительны. Это, на мой взгляд, довольно короткий и скудный код. Если мы говорим о привязке и проверке данных формы React, это мечта разработчика.
Что там на самом деле происходит?
Мы начинаем с вызова ловушки useForm и передачи в него значений по умолчанию. useForm - это то место, где происходит большая часть упомянутого волшебства. Он также кое-что возвращает - values , useInput hook и isValid логическое свойство. (Он возвращает больше, но пока не буду вас путать, поскольку мы будем использовать только эти 3.)
const { values, useInput, isValid } = useForm({ username: '', email: '' });
- values - объект значений изменяется в реальном времени. Это означает, что вам не нужны обработчики onChange и setState, все это происходит внутри наших хуков, которые просто позаботятся о том, чтобы вы получили единственное, что вас интересует - ваш ценности вместе.
- useInput - ловушка, которую вы используете во входных данных, как тот, который я показал ранее. Первый аргумент - это имя входа, которое также является свойством, к которому будет осуществляться доступ в объекте values . Второе свойство - это проверка, которую вы можете передать либо как строку, либо как объект.
<TextField label="Username *" {...useInput('username', 'isRequired')} />
- isValid - логический результат проверки. Я использую его, чтобы отключить кнопку отправки, чтобы пользователь мог видеть, что он не может отправить форму.
<Button type="submit" disabled={!isValid}> Submit </Button>
Это действительно все, что нужно для создания простых форм.
Если вас интересует код, читайте дальше. В противном случае вы можете прокрутить статью до конца, чтобы просмотреть живую демонстрацию.
useForm (defaultValues, invalidAttr)
Итак, я сказал вам, что мы передаем в ловушку useForm значения по умолчанию. Мы также можем передать второй атрибут - объект - который будет нашими свойствами ошибки, которые будут добавлены к входным данным, если они недействительны. При использовании компонентов пользовательского интерфейса материала мы заинтересованы в добавлении error = ”true” к нашим компонентам, как это
<TextField error/>
Но что, если мы используем Bootstrap, Bulma, собственный CSS или другой фреймворк пользовательского интерфейса? Что ж, мы можем передать наши варианты простым способом:
const { values } = useForm( { name: '' }, { className: 'is-invalid' } )
Вы можете выбрать любой нужный недопустимый атрибут или объединить несколько значений.
Это весь мой код useForm. Как видите, существуют переменные values и setValues (для хранения и изменения значений), formErrors и setFormErrors (для хранения и изменения ошибок) и смонтированный логический флаг.
Большая часть того, что делает этот настраиваемый хук useForm, - это
- он контролирует смонтированное состояние (я добавил это, потому что кнопка была изначально действительна в течение миллисекунды, и я хотел, чтобы она сразу не мигала как недействительная - это отвлекало)
- он создает другой (useInput) хук и передает его свойства в (например, formHandler или errorHandler, чтобы дочерний ввод мог переписать значения или наши свойства ввода ошибок)
- он возвращает values , функцию для ручного setValues (если вам нужно сбросить форму после отправки), useInput, errors (объект с поля и их первые неудовлетворенные правила) и логическая переменная isValid, для которой требуется, чтобы форма была смонтирована, а ошибки должны быть пустыми, чтобы быть истинным .
useInput (имя, проверка)
Итак, useForm дает нам доступ к этой ловушке useInput, которая контролирует наши значения, ошибки и принимает имя ввода и критерии проверки.
Здесь мы занимаемся многими вещами. Мы хотим знать, был ли ввод сфокусирован, изменен, размыт и т. Д. - для управления значением, а также отображением ошибок. Мы не хотим, чтобы поле ввода светилось красным, например, если вы еще не прикасались к нему. Часть кода просто устанавливает такие значения, как handleBlur или handleFocus, я не буду вдаваться в подробности.
Мы проверяем ввод сразу после монтирования, потому что нам нужно знать, нужно ли нам отключать кнопку или нет.
useEffect(() => { handleValidation(); }, [handleValidation, name]);
Мы также проверяем его каждый раз, когда меняем значение. handleValidation()
воссоздается при каждом изменении значения, поэтому массив useEffect
dependencies заботится о том, чтобы наша функция работала, когда мы этого хотим.
И нам также нужно следить за внешними изменениями нашего собственного ввода, если вы используете setValues из useForm где-то в вашем компоненте.
useEffect(() => { if (value !== formValue) { setValue(formValue); setIsTouched(false); setIsFocused(false); } }, [formValue, value, setValue, setIsFocused, setIsTouched]);
Это вызывает изменение значения и вызывает проверку.
Также изменяется родительский значения - handleChange ожидает получить событие (или любой объект, имеющий свойство target: {value}) и вызывает родительскую функцию setFormData для обновления весь объект.
const handleChange = useCallback(({ target }) => { const { value, checked, type } = target; const newValue = type === 'checkbox' ? checked : value; // using dot helps us change nested values let data; const isNested = name.includes('.'); if (isNested) { dot.override = true; data = dot.str(name, newValue, { ...formData }); } else data = { ...formData, [name]: newValue }; setValue(newValue); setFormData(data); }, [setValue, formData, setFormData, name]);
Обновление: проверка также может работать со значениями вложенных имен с использованием библиотеки dot. Если вам интересно больше, вы можете увидеть демонстрационное приложение в конце статьи. Он используется так:
<input {...useInput('member.username', 'isRequired')}>
Сама проверка передается на аутсорсинг моей функции validate, которая либо возвращает первое неудовлетворенное правило, либо значение null. Если у нас есть невыполненное правило, мы добавляем name ввода и правило в наш объект ошибки, который находится в ловушке useForm.
const handleValidation = useCallback(() => { const isValid = validate(value, validationRules); setIsValid(isValid); handleError(name, isValid); }, [setIsValid, validationRules, name, value, handleError]);
проверить (значение, проверка)
export function validate(value, validation) { const fieldsToValidate = {}; let trimmedValidation; switch (typeof validation) { case 'object': Object.keys(validation).forEach(property => { fieldsToValidate[property] = validation[property] }); break; case 'string': default: if (!validation.length) return null; trimmedValidation = validation.replace(/ /g, ''); trimmedValidation.split(',').forEach(fieldName => { fieldsToValidate[fieldName.trim()] = true; }); } // check whether we do need to validate at all const isRequired = fieldsToValidate.isRequired || fieldsToValidate.isEmpty === false; if (!value && !isRequired) return null; let unmetValidationRule = null; let isValid = true; Object.keys(fieldsToValidate).forEach(rule => { // don't proceed if we're already invalid if (!isValid) return; const options = fieldsToValidate[rule]; switch (rule) { case 'isRequired': if (!value) isValid = false; break; default: switch (options) { case true: case null: isValid = validator[rule](value); break; case false: isValid = !validator[rule](value); break; default: isValid = validator[rule](value, options); } } if (!isValid) unmetValidationRule = rule; }); return unmetValidationRule || null; }
Итак, в основном, если я получаю строку, я анализирую ее на объект, и если я получаю объект, я просто перемещаю значения. Я использую переменную fieldsToValidate, которая после заполнения выглядит так
{ isRequired: true, isLength: { min: 6 } }
учитывая, что он принадлежит входу, который выглядит следующим образом (вы можете передать объект проверки, если вам нужны более сложные правила)
<input {...useInput('username', { isRequired: true, isLength: { min: 6 } })}>
Как видите, я создал собственное правило isRequired. Мне было удобно использовать, если я хочу пройти проверку строки. Я использую пакет npm validator, в котором есть множество функций на ваш выбор. Я также не проверяю поля, если в них нет значения, а в правилах сказано, что оно не требуется (с помощью ‘isRequired’: true или ‘isEmpty’: false) валидатора. Затем я возвращаю невыполненное правило или null. Потомок добавляет или удаляет свою ошибку из объекта ошибки и .. вот и все!
Другой пример формы
У этого есть настраиваемые атрибуты ошибок, используется метод валидатора isEmail, а также isInt. Как видите, вы можете передавать методы, которые принимает валидатор. Он может не работать со всеми их методами из-за переданных опций / параметров. Этот код можно улучшить, и вы определенно сможете это сделать, если захотите. А пока мне нужно было что-то маленькое и простое.
Код с открытым исходным кодом
Если вы хотите использовать эту проверку формы, она находится в моем репозитории на GitHub - https://github.com/DJanoskova/React-validator-demo. Вам не нужно собирать код из этой статьи.
Есть также другие примеры и живая демонстрация, которые находятся по адресу https://react-form-hook-validator.herokuapp.com/.
Спасибо за чтение, удачного подтверждения!