Как стать автором
Обновить

Комментарии 34

Приятная статья. Добавлю себе.
Что еще можно сделать?
Дополню, что можно еще разделить InputField на 2-3 компонента, например так:
  const CustomInput= ({ type, value, onChange }) => (
      <input type={type} value={value} onChange={e => onChange(e.target.value)} />
  );

  const FormFieldWrapper = ({ label, children }) => (
    <label>
      {label}
      {children}
    </label>
  );

  const InputField = ({ type, label, value, onChange }) => (
    <FormFieldWrapper label={label} >
      <CustomInput type={type} value={value} onChange={onChange} />
    </FormFieldWrapper >
  );

В FormFieldWrapper обычно не только label, но и текст ошибки валидации, еще что-нибудь. Эту обертку можно использовать для input, select, checkbox, вместо дублирования кода в каждом контроле. Понятно, что в небольших проектах подобное не нужно, но в больших проектах может пригодиться.
Формы из замоканных конфигов

Вообще-то если б люди побольше реализовывали нормальную архитектуру, то то, что вы назвали «формой из замоканных конфигов» называлось бы MVP (не тот MVP, а Model-View-Presenter). Разумеется, ваш код на полноценный MVP не тянет, но базовая идея — именно такая. При нормальной реализации позволяет полностью разнести всю бизнес-логику в UI, и унылую визуализацию этого всего посредством какого-нибудь реакта. И при желании потом эту визуализацию переписывать хоть каждый день на новый фреймворк.

Когда-то давно я работал с PHP. В то время (надеюсь и сейчас) считалось дурным тоном мешать в одну кучу код и разметку. Правильным же был подход писать или использовать шаблонизаторы. Сейчас в React вижу обратную картину: возврат именно к мешанине кода и разметки, сущность "разметка" разорвана на множество мелких фрагментов, раскиданных по js-файлам.

Вся соль в том, что чисто декларативно (т.е. только через html+css) некоторых вещей просто не сделать. И нет практических причин за этим гнаться. Итого визуализация — это не только «разметка», но еще всегда и «код», и это в принципе нормально. Плохо не это, а то, что мало кто задумывается над функциональным разделением кода — того, который нужен для визуализации (и которому самое место быть где-то рядом с разметкой), и кода, который представляет собой ценность для бизнеса (описание предметной области и логику операций на ней).

Согласен. Поэтому и существуют в разных языках разного рода шаблонизаторы. Да и в том же PHP есть Smarty, кажется весьма популярный среди непрограммистов.

Незнакомым с тем, о чём вы пишете, приведу пример (т.к. недавно снова на такое наткнулся):


screenshot


  1. css
  2. html
  3. attributes-css
  4. attributes-javascript
  5. SQL
  6. PHP.

Для полного счастья не хватило <script/> тега :)

Это называется "компонентный подход". Раньше не было удобного способа разбивать код на основе логических связей. Всё что мы могли — это разделить HTML, CSS и JS.

Вы только что полностью передали мое впечатление от реакта.


20 лет эволюции замкнулись в круг.


В результате каждый проект на реакте, который я видел, содержит заново написанную, неспецифицированную, глючную и медленную реализацию половины Common Lisp (зачеркнуто) angular/vue

Это пол беды.
С появлением хуков, они пошли дальше. Все в одну функцию поместили.
Посреди компонента-функции, вместе с кодом, который выполняется при каждом обновлении компонента, пишется код, который выполняется при mount, umount и при изменении зависимых переменных.

Ну не получается у них сделать шаг вперед без шага назад)
Появились пользовательские элементы (компоненты), но в них стали смешивать логику и разметку.
Появился более-менее нормальный способ вынесения повторного кода из компонентов (custom hooks), но тут же стали писать логику, хуки, jsx в одной функции.
Согласен, хуки — не только перемешивание логики и разметки, но еще и мешанина асинхронно выполняемых функций и синхронного рендеринга. Хуки — по факту обычные setTimeout:

let prevMyProp = null;
function Component({ myProp }) {
  setTimeout(() => {
    // запрет выполнения при совпадении параметров, как в хуках [myProp]
    if (prevMyProp === myProp) return;
    // логика
    1 + 1;
    // для будущей проверки на совпадение
     prevMyProp = myProp;
  }, Component.didRenderTimeout)

  React.useEffect(() => {
    1 + 1;
  }, [myProp])

  return <div />
}


Но так как Component.didRenderTimeout — динамический параметр, а проверка на изменение пропов — довольно распространенная операция, разработчики сделали хелперы для этого, названные «хуками». Этот шаг по сути небольшой, но он вызвал неоправданный ажиотаж у людей со складом ума «вышло позже — значит надо использовать, все остальное легаси!», которые еще и толпы создают на премьерах новых айфонов) Но у functional components + hooks масса недостатков:

  • раздувание чистой функции рендера
  • создание функций, интенсивно использующих замыкания и друг друга (один хук получает информацию из другого хука, который вытаскивает ее из ref, сохраненного в стейте компонента и вызывает setState). Одна-две композиции не принесли бы ощутимого вреда, но я вижу в проектах по пять-десять тесно связанных функций, походит на характерный для jquery клубок-пятисотстрочник.
  • синтаксис, основанный на «соглашениях». Вместо понятного жизненного цикла (до рождение, после рождения, после обновления и т.п.) с именованными названиями пришли useEffect (использовать эффект?) с неименованными параметрами (первый — асинхронный колбэк, второй — массив с управляющими вызовом асинхронного колбэка элементами), useState (это название годное), который возвращает массив с первым элементом = значение, вторым = функцией для проставления значения...
  • ухудшение производительности и проблемы с утечками памяти (когда функции создаются непосредственно в рендере и работают с внешними данными)
  • оформление в functional components и отдельных функциях привело к неструктурированности компонентов и обилию ручных пробросов (контекста и других функций)


Таким образом, React сделал шаг в сторону «фреймворкоризации», предложив определенные синтаксические конструкции со своими хелперами (т.е. «соглашения») вместо бытия библиотекой по согласованию переданных данных и DOM-представления. Не смотря на уже 3 довольно объемных проекта, с которыми поработал на хуках+FC, вернулся к классовым компонентам и радуюсь преимуществам:

  • инкапсулированная логика с методами, в которых уже есть доступ к props, context и другим методам
  • чистые render-функции
  • человекопонятный жизненный цикл
  • простота установки переменных (ref / не ref)
  • одинаковые функции типа this.handleChange без оборачивания в дополнительный useCallback (бонус — легкость удаления обработчиков вроде addEventListener)
  • возможность применения декораторов в @-синтаксисе как глобально к классу, так и к методам


Вот теперь думаю, что если бы классы вышли после хуков — то побежали ли бы все с криком «о как круто столько преимущество долой эту всю кашу!» переписывать проекты на классы? Почему-то кажется, что да
Почему-то кажется, что да

Думаю нет. Писал пару лет на классах, страдал. Перешёл на хуки 2 года назад, долго не мог понять hook-way. Разобрался. Стало значительно удобнее. Возвращаться на классы не планирую.


Компоненты на классах плохи не ввиду ключевого слова class, а ввиду того, что в React не сделали никаких удобств по работе с методами жизненного цикла. Одна и та же функциональность вынужденно оказывается размазана по классу и путается с другими. Когда то были миксины, но их выпилили. В итоге если разработчики не сильно круты, а задача не тривиальная, то эти классы, как правило, превращаются в сложноподдерживаемое месиво. Хуки же сгруппировать в древо вызовов весьма тривиально.


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


Вы кстати забыли написать про самую хохму. Хуки очень требовательны к пониманию разработчикам основ языка (вроде замыканий, ссылок на объекты) и мемоизации. В то время как на классах относительно успешно мог писать человек который JS видел раньше только на картинке. С хуками он сразу попадёт в множество неочевидных ловушек. Ну и попадают. А хохма в том, что React привнёс хуки ввиду того, что "this это слишком сложно для наших программистов, они не понимают эту концепцию, и мы придумали более простой и элегантный путь"… Который в итоге взрывает мозг даже программистам среднего уровня.


Мне самому же хуки очень нравятся. Другая идеология и другие правила игры. В какой-то степени это напоминает решение загадок. Как бы так всё организовать чтобы без лишних рендеров, код был попроще, и всё работало как надо?! Хуки дают ряд инструментов и тасовать их… в общем интересная штука. На классах этого всё было не нужно. Всё было убого (особенно всякие componentWillUpdate), но довольно очевидно.

в React не сделали никаких удобств по работе с методами жизненного цикла. Одна и та же функциональность вынужденно оказывается размазана по классу и путается с другими. Когда то были миксины, но их выпилили.
Кстати, я делаю issue в их rfcs репозиторий на эту тему. Изменил структуру классовых компонентов — отделил render от компонента и убрал из компонента пользовательскую логику в массив объектов компонента. То есть логика не размазана по компоненту, а вынесена в отдельные изолированные друг от друга объекты, как хуки.

Реализация с примерами использования

Введение. Описание, почему предлагаю

Описание реализации

Не ожидаю, что после этого захотят вернуться к классами, но надеюсь хотя бы некоторые предложения будут реализованы в будущем в каком-либо виде.
Да, в классовых компонентах есть композиционная проблема — если на componentDid(Mount|Update) нужно выполнить несколько разнородных операций (поставить несколько обработчиков, вызвать асинхронную логику), то они указываются одноуровнево, что приводит к раздуванию самого класса. Предлагаемые вами behaviors, как понимаю, позволяют задавать эту логику в отдельных сущностях, которые затем «склеиваются» в основном компоненте, при этом учитывается логика Реакта и проброс в render, что удобнее композиции отдельных классов. Идея, в целом, очевидная, и мне тоже странно, что разработчики Реакта предпочли создать хуки вместо упрощения работы с классами.

Согласен с faiwer, что у хуков множество ловушек, и написать низкопроизводительное некрасивое кодом приложение стало намного проще. То, за что я любил Реакт — отсутствие своего мета-языка и строгих правил, очевидность жизненного цикла, простота в целом, с хуками начало уходить и библиотека превращается в «ни туда — ни сюда» — не фреймворк и не библиотека рендеринга.

«Месиво в классах»? Вы, наверное, еще не повидали «месиво в хуках», см. скоро в каждом втором проекте. Решается прямыми руками)
Решается прямыми руками)

Ну, дык, это ж самый дефицитный товар во вселенной. Можно тогда сразу написать — проблема не решается :D


Да, месиво в хуках пока не встречал. Стараемся руководствоваться базовыми принципами чистого кода, а их куда проще поддерживать когда нет позвоночника (классов).

По-моему, все действия после первого примера с кодом являются нарушением KISS и YAGNI. Особенно handleChange со строкой в параметрах :)
А DRY больше про «не дублируйте логику», а не про экономию места.
Насчет первого, да, так и есть. Идея в том, что мы сначала пишем все отталкиваясь только от KISS. Затем применяем DRY, но, так что-бы абстракции не только переиспользовали логику но и упростили чтение.
Cравните 2 примера:
e => setLogInData({ ...logInData, nickname: e.target.value })

handleChange('nickname')

Первый — императивный: возьми данные из события и положи в состояние logInData.nickname.
Второй — декларативный: обработай изменение nickname.

DRY — именно про «не дублируйте логику», я полностью с вами согласен, я это и пытался донести. В каком месте вам показалось, что я говорю об экономии места? Я это место поправлю.
В случае с handleChange переиспользуемой логикой является:
Возьми данные и положи в logInData[name]

А насчет строки в параметрах, вы просто не привыкли к каррированию :) На мой взгляд, это читается отлично
Если этих аргументов вам недостаточно, прочитайте статью, в которой эта тема раскрыта шире

Несмотря на то, что аргументация статьи в целом примерно правильная, я не могу перестать удивляться тому, как люди всерьёз рассуждают про медленный dependency-checking механизм и лишнюю аллокацию в useCallback, useMemo, Memo, PureComputed. Простая математика мне говорит, что даже если вы покроете вообще всё ваше приложение мемошками и useCallback-ами, и они всегда будут срабатывать зря, вы всё равно не получите ощутимой просадки по производительности. Просто потому, что проверка по ссылке пары значений это околонулевые cost-ы. В то же время как даже 1 лишний рендер на 10 нужных перекроет все эти "потери", т.к. число вызванных аллокаций и проверок в virtualDom радикально превысит все эти мемоизации.


Т.е. правильнее говорить о том, что обычно вешать повсюду мемоизацию это добровольно усложнять кодовую базу, а это имеет куда более высокую цену, чем та экономия на спичках, о которой идёт речь.


Ну и можно отметить, что для того, чтобы упереться в тормоза вызванные отсутствием мемоизации нужны большие объёмы. Какие-нибудь графики, большие таблицы, большие списки сложных компонент.


Отдельно не могу не отметить, что "повальной" мемоизации могут быть и другие плюшки:


  • Если у вас сложным образом устроена работа с данными и, скажем, есть нормализованный стор с иммутабельными данными, которые нужно собирать по частям, то без мемоизации вы легко получите rerender всего приложения разом на любой чих. Неспроста react-redux connect по-умолчанию мемоизирован.
  • Если у вас и так всё иммутабельно, то повальная мемоизация упрощает дебаг. Вы по-умолчанию исходите из того, что ничего не ререндерится без реальной необходимости. И получив какой-нибудь хитрый баг в хитрых условиях со всякими race-condition его куда проще дебажить, когда у вас вместо 100 render-ов всего 1 :-D

Мы используем мемоизацию везде кроме листьев vdom-древа. Во многом с точки зрения дисциплины в коде. И "брат не умер" :)

Вспомнилось. Кажется Gitlab написан на Vue, ну да не суть. Про сам подход. Бывает приходится проводить code-review какого-нибудь большого сложного рефакторинга. Который просто вынужденно делается одной задачей. А это легко приводит к 6000+ строкам изменений. Да этого следует избегать, но далеко не всегда удаётся.


Так вот открывая такой review в gitlab натыкаешься на то, что сам browser работает очень быстро и обрабатывает чудовищное количество domElement-ов играючи. Но как только дело касается SPA, который этим всем заведует, то на любой чих привет фризы по 2-10 секунд. Реальных объективных причин для них нет.


Т.е. у ребят 2 проблемы:


  • Нет механизма постраничной обработки, которая позволит решить эту проблему даже с медленным SPA
  • Написанный по принципу "wait until the abstraction/optimization is screaming at you" SPA приводит к ужасному usability. Или ещё хуже, как у Discord. Читая ту статью, я испытывал ужасный испанский стыд. Всех этих ошибок ребята могли избежать, если бы включили мозг не спустя много лет существования проекта и мириад жалоб от пользователей, а просто сразу правильно делая. Это всё ведь есть даже в документации.

В общем если вы пишете большой и серьёзный продукт, то имхо, лучше если вы везде лишних useCallback и useMemo наставите, которые в половине случаев у вас развалятся, чем если вы будете писать что придётся "wait until the abstraction/optimization is screaming at you".


Эти жутко тормозящие SPA уже скорее данность. Чаще всего там проблема в чём-то совсем простом, на что или забили с самого начала, или почему-то даже не знали.


P.S., sorry, накипело :)

Честно говоря, не хватает типизации. Типизация снижает количество ошибок. У инпутов нет тип отображения(пароль не должен отображаться как строка). Каждый контрол может иметь индивидульные особенности. Конструкция a => b => c => — уменьшает размер кода, но сильно снижает читаемость. Хотя может дело привычки. Общая функция обработки изменения значения поля, которая определяет какое поле изменилось — для конкретного случая норм. Но она часто будет приносить сложности контролирования ошибок, лучше использовать отсылку на объект поля
Как по мне, то использовать useCallback и useMemo нужно тогда, когда они используются в dependency массивах других хуков useEffect, useCallback и useMemo. В большинстве остальных случаев это не имеет смысла.
Хорошая, нужная статья, много правильных мыслей, хоть и есть несколько спорных моментов: например, совершенно непонятно зачем приплетён useCallback, который вообще совсем для другого, да и аргументы против генерации формы из конфига довольно странные, т.к. решаемая здесь задача этого вообще не предполагает (к тому же, повторное использование InputField с разными параметрами никак не противоречит DRY). Генерация форм — это вообще совершенно другая задача, которая должна решаться не здесь, а где-то на других уровнях абстракции, если уж вдруг возникнет необходимость в реализации именно такого подхода.

Ну и если уж всё это претендует туториал с примерами реального, а не псевдо-кода, то этот код лучше, наверное, сделать рабочим? В начале статьи всё было правильно, но после рефакторинга куда-то потерялся мерж с текущим стейтом.

// handleChange курильщика.
// В отличии от классового setState(), 
// здесь потеряются все данные стейта, 
// кроме назначаемого текущего поля. 

const handleChange = fieldName => fieldValue => {
   setLogInData({
      [fieldName]: fieldValue,
   });
};


// handleChange здорового человека.
// Вот тут все будет работать правильно

const handleChange = fieldName => fieldValue => {
   setLogInData({
      ...logInData,
      [fieldName]: fieldValue,
   });
};

А вообще, по сути говоря, вместо мерджа в 1 state, нужно просто 3 разных useState-а.

конечно, но тут, видимо, сильна ещё инерция старого подхода, используемого в классовых компонентах, с одним общим state и setState()
По сути, модель предлагаете делить на 3 независимых поля.
И какие преимущества 3 разных стейтов вместо общего?
Вот недостатки я вижу:
* При обновлении нескольких стейтов за раз, компонент будет обновляться также несколько раз, что как минимум осложнит дебаг.
* дополнительное возня, если все эти стейты нужно передавать в другую функцию. Так как вместо одного объекта.
* в массивы зависимостей хуков вместо одной зависимости придеться добавлять несколько.
  • При обновлении нескольких стейтов за раз, компонент будет обновляться также несколько раз, что как минимум осложнит дебаг.

It depends. В некоторых контекстах это вызовет ререндер. В некоторых эти изменения группируются. По сути если у вас сложная работа со state-ом, то есть useReducer.


дополнительное возня, если все эти стейты нужно передавать в другую функцию. Так как вместо одного объекта.

Этот аргумент валиден только тогда, когда вам действительно оно нужно. Я могу в противовес сказать, что вы зато не передаёте куда не надо, возможность делать то, что не надо. Это действительно ценно.


в массивы зависимостей хуков вместо одной зависимости придеться добавлять несколько.

А почему вы записали это в минус? Это же жирный плюс. Вы за clean code и best practice или за херак-херак-и-в-продакшн? :)


Преимущества очевидны — мы не мешаем тёплое с красным. Меньше багов, проще код. Лучше разделение ответственностей.


По сути вот как раз группировать нужно тогда, когда возникла в этом потребность. И даже в этом случае хорошо бы задуматься над тем, как бы не тащить лишнее туда, куда не надо.

It depends. В некоторых контекстах это вызовет ререндер. В некоторых эти изменения группируются. По сути если у вас сложная работа со state-ом, то есть useReducer.
Вот как раз в случае форм, я считаю, что автор правильно сделал, что использовал один объект. Хотя, как вы написали, нужно было useReducer использовать.

А почему вы записали это в минус? Это же жирный плюс. Вы за clean code и best practice или за херак-херак-и-в-продакшн? :)
Помимо хуков это уже где-то 10-лет используется, что дробление состояния небольшой сущности/компонента относится к clean code и best practice? Я не спорю, что иногда это нужно и более правильно.

Преимущества очевидны — мы не мешаем тёплое с красным. Меньше багов, проще код. Лучше разделение ответственностей.
Если брать пример формы из статьи, там нет теплого и красного, только теплое. И вы пишите, что лучше теплое отделять от теплого) Я вижу только, что с ростом формы, багов при таком подходе будет как раз больше и код сложнее. Получим кучу состояний, изменения и подписка на изменения которых разбросаны по всему компоненту. Конечно, тут зависит от того, как написать, но думаю, что в большинстве проектов будет именно так.
Спасибо за комментарий, я поправил handleChange.
Избегайте описывания ваших форм с помощью конфигов

Вот не согласен максимально. В итоговом варианте с портянкой в разметке получилось, что компонент — черная коробка <Form /> с неизвестным набором полей и полностью скрытой логикой, хранящейся во внутреннем стейте. И вот навскидку то, с чем придется столкнуться при таком подходе:

  • Для проброса initialValues в форму придется копаться в реализации и передавать объект с согласованными с именами полей параметрами. Если добавятся propTypes / TS Interface, то это будет дубляж
  • Типы передаваемых initalValues, опять же, нужно согласовывать (для селектов / чекбоксов)
  • При добавлении влияющей на пропы конкретного поля логики (включение / выключение отображения, disabled, optional, смена label) придется внутри писать массу if-конструкций
  • Динамическую раскладку придется поддерживать вручную (чтобы если поле пропало, то следующее встало рядом с ним)
  • Хотелось бы посмотреть, как будут реализованы валидации и синхронизация их с backend (когда в ответ присылается к примеру { email: «INVALID»} в слое-контроллере, и нужно вставить эту валидацию в поле в добавление к имеющимся, подсветить его с указанием локализованной ошибки, прокрутить страницу к нему)
  • Как будут исключаться поля с пометкой optional из валидаций, а с disabled из отправляемых данных на бэк
  • Как будут контролироваться динамически добавляемые поля
  • Как сторонние компоненты и другие формы смогут влиять на эту (получать значение, проставлять, менять конфигурацию полей)


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

В общем, я бы не использовал описанный в статье подход даже для таких казалось бы маленьких и супер-простых форм, потому что в продакшене будет совсем другая история, и следовало бы начать с проектирования грамотного конфига. Фичи в него можно добавлять постепенно и обрабатывать централизованно с минимальным вмешательством непосредственно в компоненты уже сделанных форм. Кроме того, убежден, что грамотно спроектированный «скелет» (с глобальным стейтом, механизмом валидаций, конфигом полей, оптимизацией рендеринга) не является оверинжинирингом, а служит базой для масштабирования, под какими бы yarniенками ни маскировался подход «слепить по минималке и перекопировать по сотне раз с разной реализацией», в будущем все равно кому-то придется сделать нормально.

Накипело, столько уже проектов повидал с такими вот формами. Не надо так. Но и превращать конфиг в json-схему генерируемую в cms по созданию форм, разумеется, тоже не надо — это как раз оверинжиниринг, а не создание дополнительного объекта.
Здравствуйте DmitryKazakov8.
Только сейчас прочитал ваш комментарий.

React — библиотека для создания пользовательских интерфейсов. Конфиг — это дополнительный слой абстракции который описывает страктуру нашей формы. Использование конфига для описывания структый является не природным для React, поскольку в React для этого есть render функции, jsx и эти интрументы прекрасно справляются со своей задачей.

Если вы написали конфиг вам все-равно придется написать функцию, которая превращает конфиг в react элементы. Постепенно вы, или ваши колеги, добавите в конфиг кучу полей которые будут управлять рендерингом этого же самого конфига и придумаете свой фрейморк, в котором можно будет разобраться только прочитав исходный код — ту самую функцию которая преращает конфиг в дерево React-элементов. А код этой функции неизбежно будет сложным, ведь он проделывать целую кучу всяких проверок и предположений для каждого элемента из конфига а потом исходя из этого рендерит компоненты с абсолютно разными prop-ами.

Всего этого можно избежать описываю структуру напрямую в рендере. Вы думайте что это как-то кординально все меняет, но это не так. Это все тот же конфиг только записанный в виде JSX и вы сразу, напрямую, управлете рендерингом элементов в нем, избегая дополнительных неявных связей и сложного кода для рендерига в итоге.

Дальше по пунктам:
  • initialValues, interface, propTypes это то, с чего нужно начинать разработку формы, в этом случае копаться в реализации при их добавлении не придется. Если же это проект в котором не используется ни одно ни второе, что печально, то копаться в любом случае придется
  • Не совсем понимаю, о каких initalValues для select/checkbox идет речь, у нас ведь контролируемая форма. Как я писал выше типы value и onChange для UI компонентов стоит держать одного типа в этом вам поможет TS, просто будет ругаться если передадите что-то не то. Я так и не понял в чем здесь проблема
  • Не знаю как статический конфиг помогает вам делать динамическую раскладку, возможно это какой-то фреймворк вам помогал с этим, но не конфиг. Все что касается динамики в render функциях делать на много удобнее чем в статическом массиве объектов
  • При использовании конфигов if конструкции никуда не денутся, они просто останутся в функцию которая рендерит конфиг, что сильно ее усложнит ведь там плотность этих if конструкций будет зашкаливать. Условный рендериг абсолютно нормально и удобно делается в React, вот статья в доке. А насчет disabled так вообще никаких проблем, просто передайте его пропом в свой компонент
  • Как я писал в начале статьи валидацию эта статья не покрывает просто что-бы не быть слишком длинной. Но если в 3-х словах, то мы просто создаем еще одно состояние для ошибок и когда сервер возвращает ошибку или клиентская валидация возвращает ошибку мы сетим ошибку в поле с нужным именем и тектом ошибки, этот текст передаем в компонент который рендерит ошибку (обычно это просто prop error в нашем InputField). То есть у нас есть просто чистая функция которая принимает данные и возвращает ошибки. Аналогично тому что описано в документации популярной библиотеки для работы с формами в React Formik
  • Ни каких пометок optional на полях формы нам не нужно, функция валидации сама знает какие поля обезательные, какие нет, и какие еще проверки нужно сделать для каждого поля
  • Если под «динамически добавляемыми полямы» вы имеете ввиду массив с полями формы, то можете посмотреть здесь. Если нет, то это обычный условный рендеринг
  • «Сторонние компоненты и другие формы смогут влиять на эту» очень просто, это ведь обычный React компонент и у него есть props, context(не рекомендую), redux и все что вы сможете придумать


Конечно могут быть случаи, когда вам нужно писать конфиг. Например если вы пишите какой-то конструктор для форм, формы должны сериализироваться и десериализироваться или используется готовая библиотека работает с таким форматом и вы ее любите, то это имеет смысл. Тем не мение в туториалах популярных библиотек для React: Formik, Final Form для описывания структуры формы используются render функции и нигде не рекомендуется использовать конфиги.
Хранить конфиг в jsx — это как хранить его в верстке, доступ к конфигурации каждого поля сложен и медленен, и вместо единообразных конфиг-объектов приходится дублировать код от формы к форме. Если у вас будет 50 таких форм, и нужно изменить какую-то логику, придется заниматься этим в каждом конкретном компоненте, который для всего остального приложения является «черным ящиком». Это в целом очень неудобный паттерн, и то, что он используется в нескольких опенсорс-решениях для упрощения интеграции в свой проект, ничуть не свидетельствует о том, что это решение лучше. Наоборот, проблем при интеграции этих библиотек по опыту больше, чем от использования движка на конфигах.

Для меня Реакт — библиотека, синхронизирующая состояние между js-стейтом и DOM с бонусом в виде жизненного цикла компонентов. JSX в качестве хранилища стейта крайне неудобен ввиду сложности сериализации-десериализации, он является лишь описанием, как js-структуры корректно перевести в DOM.

Насчет кучи проверок и ифов в конструкторе форм из конфига — вы явно перегибаете, их будет совсем немного при грамотном проектировании, а в большинстве случаев — ни одной: {formConfig.map(({ FieldComponent, fieldProps }) => <FieldComponent {...fieldProps}/>)}, но при динамической раскладке сюда добавится дополнительный слой в виде группировки полей из конфига по определенному признаку и их выведению в соответствующих размеру экрана рядах, но это несколько дополнительных строк, и уж точно «дополнительных фреймворков» не нужно.

If-конструкции уходят практически полностью благодаря переносу в конфиг в виде семантических параметров — isDisabled, isShown, isOptional, и их можно менять динамически в удобной манере: formConfig.phone.isOptional = formConfig.email.isValid() || formConfig.name.isDisabled || true, и зашивать это статично в валидатор — плохая идея. При сабмите формы легко можно пробежаться по isDisabled и исключить эти поля и из отправки на бэк, и из валидаций.

Через пропы влиять на jsx — путь к раздуванию компонентов и рассинхрону апи у однородных сущностей. А вот добавить параметр к унифицированному компоненту и сделать изменение в одной точке — что может быть проще? Если компонент рендеринга форм начинает выглядеть сложно — это композиционная проблема, а не проблема подхода.

В целом для простейших форм и если их до десятка на проекте, можно и по принципу «черного ящика» создавать компоненты и с локальным стейтом, но в перспективе все равно придется создавать унифицированное решение с более открытым интерфейсом.

В предыдущем ответе у меня была ссылка на небольшую форму
https://codesandbox.io/s/github/VladislavMurashchenko/use-prop-change-sandbox?file=/src/containers/UserForm.tsx
Интересно было бы посмотреть на ваш вариант с использованием конфига.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории