Комментарии 15
У вас типичный и сомнительный вариант с использованием обертки и клонирования элемента ради вынесения общих props-ов. Не понимаю, почему всех тянет так делать. Задача ведь состоит в том, чтобы избавиться от дублирования пропсов. А значит надо для начала посмотреть, что можно сделать с пропсами, а не с элементом.
Вот более удобный вариант, который к сожалению мало кто видит:
<Input {...commonInputProps(name)} />
В функции commonInputProps уже нужная логика с сохранением/мемоизацией куда нужно по имени поля и возврат объекта с данными именно для этого поля.
Отличное замечание! Однако отмечу, что задача изначально стояла не только вокруг управления пропсами, но и вокруг интеграции общей обертки (например как в вашем случае лучше решить кейс с выводом сообщения о текущем статусе валидации для поля формы?). Я показал в статье альтернативу (не замену устойчивым паттернам), которая может быть полезна на стыке возможностей хуков и хоков, чтобы объединить вместе работу с пропсами и одновременное создание обертки элемента для различных нужд
Не обратил внимание при просмотре статьи на задачу вокруг интеграции общей обертки.
например как в вашем случае лучше решить кейс с выводом сообщения о текущем статусе валидации для поля формы
Для этого уже действительно лучше завести отдельный компонент, как у вас FormItem. Но это уже другая задача и нет необходимости объединять разный функционал в одном компоненте. FormItem так может разрастись и усложниться со временем.
Вот более удобный вариант, который к сожалению мало кто видит:
<Input {...commonInputProps(name)} />
Да не сказать чтобы мало.. Этот способ представлен и в Формике, и в react-hook-form, причем одинаково - useForm/useFormContext возвращает, помимо прочего, функцию register, которая именно так и применяется - <Input {...register(name)} />. При этом register уже завязана на все необходимые контексты.
Описанный автором подход с оберткой FormItem, кроме подсветки ошибок и иного визуального, позволяет оптимизировать перерендеры. Например, если в контекст FormContext сложить не иммутабельный стейт, а мобиксовый стор, то при печатании букв не будет перерендера всей формы, а только лишь отдельного FormItem (при условии, что в самой форме не упоминается значение поля).
Видимо я ошибался. Теперь начинаю видеть преимущества подхода, особенно в плане оптимизации. А недостаток в виде раздувания FormItem обходится с помощью вынесения логики из него в хуки, сторы. Спасибо, что открыли мне глаза!)
На своей же практике встретил неудачное применение клонирование компонентов только ради вынесения общих пропсов, что создавало нам проблемы. Подумал, что и в статье то же самое, но оказалось, что нет.
С формиком и react-hook-form не работал. И на глаза практически не попадался подход с вынесением общих пропсов в функции. А вот клонирование часто встречалось.
Почему нельзя просто передать пропом компонент для отрисовки?
<FormItem
name="password"
component={Input}
/>
/** Делаем слияние новых и старых props, ведь мы хотим оставить возможность пробросить props в разметке снаружи */ const childProps = { ...(children.props || {}), ...injectionProps };
Оо, а разве cloneElement не делает это автоматически? По моему он мержит пропсы по умолчанию.. делать этого руками не стоит.
Вот так же ш достаточно было бы
const clonedElement = React.cloneElement(children, injectionProps)
При таком подходе вы инкапсулируете логику внутри FormItem
и это хорошо, но запрещаете ее расширять не изменяя код внутри, а это на мой взгляд плохо.
При каждой попытке расширения, мы будем явно вынуждены вносить изменения в FormItem
расширяя его поведение для всех частных случаев.
Например компонент Checkbox
принимает не value
, а checked
или при взаимодействием с полем, необходимо очищать/изменять другое поле, преобразовывать текущее. Да даже обычное маскирование сделать без костылей будет сложно. Нужно будет придумывать для каждого случая обходной путь или расширения внутри общего компонента.
Возможно именно поэтому она и не недооценена, а вполне справедливо не используется как альтернатива, тк является паттерном, который усложняет поддержку и разработку.
Все перечисленное точно так же всплывёт, если вместо сабжа сделать хок, рендер-проп или компонент-проп. Потому как это вопросы проектирования, а не используемого подхода.
Кстати, ещё пример где используется сабж - CSSTransition из "react-transition-group". Он добавляет className в свой children.
Мы говорим о разных вещах. Посмотрите на конкретный пример, компонент Checkbox принимает значение checked, а не value, что мне нужно сделать чтобы поменять название пропса для принимающего компонента. Мне необходимо использовать один из паттернов, который вы написали хок/компонент-проп, иначе говоря, то что вы пытаетесь избежать, мне все равно придется использовать)
Этот подход по сути одна из реализаций паттерна миксин, а все новое, хорошо забытое старое и примерно в 2013/2014 году весь мир JS был построен на этом паттерне, можете поискать статьи того времени почему это было плохо/хорошо и сделать выводы.
При этом я и не говорил, что подход плохой, я говорил, что его сложно расширять и в теории сложно типизировать. Для задач, где это не требуется, он вполне подходит.
Спасибо за интересный взгляд, но я не совсем согласен с вашим мнением. Вы говорите о проблемах с расширением логики относительно других подходов, но я совершенно не понимаю в чем она заключается. Когда мы хотим преобразовать поведение хука или хока, то взаимодействуем с ним через его аргументы или используем композицию (зависит от реализации конечно же, но мы усредним). Даже в рамках данного упрощенного примера нам ничто не мешает использовать props для Form.Item
(те же аргументы функции), чтобы расширить его поведение, а также композицию. При этом мы даже имеем общий контекст, в котором можем распространять общую логику сразу среди всех Form.Item
, чтобы избежать необходимости модификации каждого отдельного компонента.
Что касается кейса с checked
, то конкретно для компонента формы предусмотреть специфичное поведение для некоторых групп взаимодействующих компонентов - это, на мой взгляд, нормально. Однако если вы захотите применить подход за пределами описанного в статье примера и попробуете организовать таким образом код в рамках какой-то другой задачки, то сможете для себя внезапно открыть, что value
не обязательно может быть примитивом. В зависимости от реализации value
может представлять целый объект настроек, который будет сохраняться в общем контексте и обрабатываться соответствующим образом. Вас ведь не смущает общение web-worker'ов с main-tread через postMessage
в формате преимущественно строк или банальная работа с localStorage
таким же образом, так почему бы не посмотреть на это под немного иным углом? В ваших руках огромное кол-во свободы, остается сделать только славную реализацию
Честно, не знаю как описать в чем заключается проблема другими словами, все описал выше и вы ведь не опровергаете мои слова, а подтверждаете их)
Проблема заключается в том, что есть всего 2 пути масштабирования этого подхода, либо добавлять каждый частный случай в Form.Item, либо для каждого частного случая делать свой Form.Item. Если частных случаев будет 100, у вас либо Form.Item будет принимать 100 параметров, либо будет 100 разных Form.Item. Либо придется использовать один из паттернов от которых пытаетесь сбежать.
P.S. Меня очень смущает работа синхронной логики через асинхронный протокол через механизм postMessage. Гугл это тоже показалось оверхедом и они написали библиотеку - https://github.com/GoogleChromeLabs/comlink
Кстати, по поводу тайпчекинга всего этого дела, выяснилось тут, что он не идеален.
Вот небольшой набросок. Проп children попробовал сделать ReactElement<...>, чтобы учитывались пропсы. Увы, создание элементов через jsx не позволяет сделать проверку чилдрена по пропсам. Создание через старый добрый createElement проверяет соответствие пропсам, но зато не всегда может проконтролировать пропсы для типа компонента..
Недооцененная альтернатива для HOC’ов и кастомных хуков в react и при чем здесь React.CloneElement?