Pull to refresh

Comments 15

Элементы формы хранят собственные состояния, и это — самая большая проблема однонаправленности потока данных в React. Именно поэтому, мне кажется, формы в Angular лучше.

Лично мне очень понравился Formik за его относительную простоту и элегантность по сравнению с redux-form и final-form
В своей библиотеке изначально я хотел сделать состояние у Field, но в ходе разработки решил все же отказаться и оставить состояние только у поля Form, в итоге это решение лишило меня множества проблем и упростило data flow. По сути инпуты подписаны на колбеки Form.
Я с вами полностью согласен — формы и их валидация должны быть декларативными. Статья, конечно, про React но я бы хотел вас ознакомить с вариантом для Vue — vuelidate
В статье я подробно не описывал возможные схемы валидации, но в своей библиотеке, как пример, я реализовал поддержку Yup, по сути можно использовать любую библиотеку валидации данных которая принимает на вход значение полей формы, а на выходе формирует объект с ошибками
Нет, нет и нет. Валидация форм не должна быть привязана к html вообще никак.

Почему вообще каждое решение пытается привязать валидаторы к компонентам? Написать десяток компонент, которые каким-то там замороченным образом трансформируют пропсы для потомков, кладут чего-нибудь в локальный стейт, и пытаются реализовать мало-мальски адекватное поведение и реагирование на события, опираясь на лайфхуки Реакта? А потом обрастают вот этими «onModelChange», «onSubmitErrors» (который внезапно не то же, что onSubmit, и вешается не на форму, а куда-то там ещё), и прочим не пойми чем, которого становится тем больше, чем дольше автор пользуется этим решением, понимая, что оно не решает какую-то очередную проблему. И при этом почему-то заявляется, что всё это дело не «задаёт модель управления» и не «требует дополнительные данные».

Форма – это состояние. Это просто кусок данных, который нужно прогнать через функцию-валидатор. Точка. Ни данные, ни валидаторы не имеют к Реакту и компонентам вообще никакого отношения. Валидация отдельно, рендеринг ошибок – отдельно. И будет вам и декларативность, и композиция, и реюзабельность, и даже юнит-тестирование можно делать.

Для валидации требуются:
  • набор предикатов (типа isEmail)
  • тексты ошибок (задаваемые вами, а не чем-то сторонним – потому что i18n)
  • механизм соединения предиката и текста ошибки в функцию-валидатор
  • механизм соединения валидаторов в функции с более сложной логикой типа «validateScheme»

И вот где и зачем здесь Реакт и компоненты?

Вот пример того, как надо правильно и декларативно делать валидацию: medium.com/javascript-inside/form-validation-as-a-higher-order-component-pt-1-83ac8fd6c1f0
Правда, автор немного чересчур угорел по функциональщине) Можно было попроще. Но саму суть подхода показывает предельно верно.
Статья как раз про разделение контроля над формой, валидацией и трансформацией данных. Если вы внимательно посмотрите на функцию `validator`, на аргументы которые она принимает и значение которое возвращает, то заметите, что эта функция совершенно не зависит от формы и реакта. В статье на которую вы ссылайтесь как раз описываются принципы по которым должна работать эта функция. Компонент `Validation` лишь предоставляет доступ к данным формы и формирует контекст для отображения ошибок.

По поводу `onSubmitErrors ` я с вами согласен, и опять же пишу про это в статье, что компоненты Field и Form должны максимально отражать проекцию на dom и соответственно иметь схожие пропы и эвенты как у элементов form и input.
Почему вообще каждое решение пытается привязать валидаторы к компонентам? Написать десяток компонент, которые каким-то там замороченным образом трансформируют пропсы для потомков, кладут чего-нибудь в локальный стейт, и пытаются реализовать мало-мальски адекватное поведение и реагирование на события, опираясь на лайфхуки Реакта?

Потому что такие компоненты в React и подобных ему VirtualDOM-библиотеках (не путайте кстати с HTML) — это общепринятый способ композиции, соответственно большинство людей им и пользуются. Другой вопрос — рационально ли это? Я бы тоже наверное реализовал вариацию где-то ближе к стору и кидался бы уже оттуда сообщениями об ошибках и флажками valid/inValid.

Приведенный вами пример неполный, в нем полученные в ходе валидации ошибки не прокидываются обратно — к визуальными компонентам, не предусмотрена асинхронная валидация и т.п. (это все несложно сделать, но говорим о том, что уже есть) Так что далеко не факт, что описанные вами ужасы не появятся как раз на этом этапе.
Если вы открывали пример из статьи на codesandbox, то вероятно пропустили асинхронную валидацию с сервера. Компонент Validation используется как контекст, связывающий значение полей формы и её валидацию

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

У меня сделано разделение на «модель данных/валидация/простые компоненты/формы».
Каждый слой выполняет только свою возложенную на него задачу.
— модель данных — данные для формы, простой объект (ключ/значение)
— валидация — обеъект валидатор и набор валидаторов. Можно добавлять свои кастомные валидаторы. Валидатор работает асинхронно, потому можно и серверную валидацию использовать (проверка емаила итд).
— компоненты — простые и тупые. Инпут, кнопка итд. У инпутов добавлен так же вывод ошибок.
— Форма — просто связующий элемент. Валидирует модель данных. Итд.

При таком подходе можно использовать валидатор отдельно, даже не в реакте.
Компоненты можно использовать вне форм.
Код становится простым и гибким.

Жизненный цикл большинства библиотек для форм выглядит так:


  1. восторг. Я сделал штуку, которая красиво и наглядно позволяет запрограммировать вот эти 5 форм.
  2. полёт нормальный. Я сделал на ней ещё 5 форм, пришлось, конечно, кое-где подпилить, что-то дописать, но пока я могу запрограммировать этим способом всё, что мне нужно.
  3. Упс, я наткнулся на сценарий, который в это API не укладывается. Выкидывать код жалко, переписывать 10 уже написанных форм некогда. Сделаю дополнительный "костыль".
  4. Упс^2. Через 5 форм "костыль" стал по объёму кода больше, чем первоначальное решение. Но переписывать 15 форм по новому нельзя, на это времени никто не выделит.
  5. Отчаяние, переписывание всего кода и т. д.

Из этого есть только один вывод: сделайте на Вашей библиотеке максимально сложную форму и убедитесь, что это --невозможно-- можно сделать.


Примеры:


  1. Поле выбора (select / radio button), где выбирается что-то непростое, требующее парсинга. Например, дата. Сколько раз эта дата превращается из Date в текстовую строку и обратно? Где живёт нормализация этой даты? Если в форме выбирают какой-нибудь объект из списка, в скольких местах у нас делается преобразование идентификатора объекта в ссылку на сам объект?
  2. А если этот список объектов динамически меняется?
  3. Несколько полей, граничные значения динамически вычисляются исходя из значений других полей. Например, диапазон дат.
  4. Исчезающие/появляющиеся поля. Например, способ оплаты, который исчезает, если у клиента 100% скидка. Как валидировать поле формы, которое исчезает из виртуального DOM-дерева, а потом снова снова появляется, если поменяли другое поле?
  5. Группа полей, которую можно использовать повторно в нескольких формах. Попробуйте сделать группу "сумма" + НДС + значение НДС, в которой внутри проверяется, что НДС вычислен правильно. А теперь попробуйте сделать группу "купленный товар", внутри которой есть эта группа, и которую, в свою очередь, можно использовать в нескольких местах.
  6. Форма, в которой валидация части данных делается асинхронным запросом на сервер.
  7. Форма со вложенными формами.

Попробуйте в порядке упражнения сделать на своей библиотеке форму ввода данных из кассового чека. Дата-время, номер смены и т. д. А потом сделайте внутри неё формы для добавления товаров/услуг, там цены и НДС к ним, общая сумма из списка товаров --может-- должна совпадать с общей суммой чека, кассира нужно выбрать из списка, сверяясь с их сменами через AJAX. Товары пусть будут уникальными, НДС зависит от типа товара, но должна быть возможность задавать его вручную.
Скорее всего, от текущей реализации ничего не останется, придётся все данные выносить в redux или mobx, всю логику валидации нести туда же.

Спасибо за полезный совет, обязательно попробую.
1) 2) 3) пример

Для реализации этих пунктов мне не пришлось вносить изменения в текущий API. Когда я реализовывал данную форму заметил, что часто для полей приходится прокидывать значение других полей или данных из стейта формы, поэтому добавил для Field проп хелпер subscribe, который позволяет выбрать данные из стейта формы и подписать Field на их изменение, без прямого прокидывания. Доступ к этим данным осуществляется через контекст Field.
5) пример

Для реализации этого кейса мне не потребовалось пересматривать предложенный в статье API. Единственное, пришлось пересмотреть работу Validation и Transform в моей библиотеке, Transform был переделан, но не кардинально. Теперь функция трансформации выглядит так:
(values, model) => { [field]: { value } }

  1. values — неполная копия model отражающая изменившиеся поля (в том числе изменённые другим Transform)
  2. model — текущее значение полей формы с предыдущим значением изменившегося поля
  3. field — имя поля для трансформации
  4. value — новое значение этого поля


Возвращает объект вида { [field]: { value } }, подобный аргументу values, этот объект будет объединён с values и передан в следующий Transform.

Validation теперь добавляет свои ошибки к hight ordered Validation если таковой есть.
Sign up to leave a comment.

Articles