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

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

Не вижу в паттерне HOC ничего хорошего, да, он позволяет композировать интерфейс и все дела, вот только он такой же неявный как и миксины. Если ты не разраб который написал HOC, то тебе придется сидеть и распаковывать компоненты на хоках как конфеты и внимательно рассматривать каждую обертку, чтоб понять как это работает, пытаться понять кто именно прокидывает этот пропс в твой основной компонент.
Есть ли у вас какая-то готовая альтернатива HOC/миксинам? Что бы предложили вы?

Неплохой альтернативой были бы объекты/компоненты в понимании Unity 3D — это достаточно удобная система.
Миксины к этому довольно близки, кстати, за исключением того что у них нет своих хуков жизненного цикла (их надо вызывать вручную).

Поддерживаю.
Проблема уже давно решена в геймдеве. Жаль, что разработчики react и vue туда не заглядывали и свои велосипеды понаписали.

Spunreal
Про wrapper hell при использование HOC уже не раз писали сами разработчики реакта.

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

Заодно зову k12th
Спорный момент вытягивания gamedev паттернов в том, что там часто разменивают DX на производиьельность. С точки зрения дизайна api хуков в реакте очень удачное решение. Код группируется и выносится исходя из решаемых задач, а не вокруг методов жизненного цикла(и не приходится иметь десятки таких методов как юнити), при этом все вызовы явные и легко делятся данными. (причем обменном данными тоже явно управляет подключающий компонент, а не конвенции)


Из минусов сответственно производительность и запрет вызова хука в условных конструкциях. (что впрочем вызвало у нас проблемы лишь однажды за несколько месяцев использования и сразу решилось разделением компонента на 2)

Ну понятно, что если перенести этот прием из юнити прям один-в-один, то, вероятно, получится не очень — все-таки задачи разные и в геймдеве сложность куда выше. Но насчет DX, кажется, дело субъективное — мне лично в юнити приятнее работать, чем с реактом.

Кстати, я когда-то давно написал для себя плагин с этим приемом из юнити. Но редко была нужда что-то выносить в отдельные сущности, поэтому забросил.
Вот как это выглядело:
объявление в самом компоненте.
class MyComponent1 extends ComponentWithBehaviors {
  behaviors: [
  new Behavior1(behavior1Props),
  new Behavior2(behavior2Props),
]}

либо вариант с передачей в компонент через родителя.
<MyComponent2 components={[
  new Behavior1(behavior1Props),
  new Behavior2(behavior2Props),
]} />

Когда нужно выносить много логики, возможно такой подход был бы хорош. Но в простых случаях, когда достаточно 1-2 миксин/HOC, немного многовато кода придеться писать.

Интересно. А это был плагин для какого фреймворка?

для реакта. Но плагин не допилен был, ну и там в базовом классе ComponentWithBehaviors довольно костыльно происходила подписка на методы жизненного цикла реакт компонента.
Выложена одна из сохранившихся промежуточных версий — github.com/sergeysibara/react-behaviours

Миксины модифицируют базовый компонент. HOC же возращает новый, а оригинальный так и останется нетронутым. (с) Расширяй, но не изменяй.


Так же с помощью HOC можно изменить логику работы библиотечных компонентов не сломав оригинал.

Я с Vue мало знаком, но вот интересно — в Vue пользовательские директивы не могут быть использованы в качестве альтернативы миксинам? Они вроде ведь тоже могут изменить логику работы, не тронув оригинал.
Директивы в основном используются для выполнения низкоуровневых операций с DOM. А ещё они так же, как и миксины, регистрируются при определении компонента, т.е. подмешиваются в оригинальный компонент. Можно сделать обёртку, которая будет содержать директивы, но это как раз и будет HOC или аналог.
Именно, теряется вся прелесть Vue, с его систематизацией.

На мой взгляд, ниже приведенная конструкция наследования, аналогично HOC, более наглядна и удобоварима:
//SecondComponent
import FirstComponent from './FirstComponent'
export default { extends: FirstComponent }

Ссылка на документацию: ru.vuejs.org/v2/api/#extends
Расписаный пример: vuejsdevelopers.com/2017/06/11/vue-js-extending-components

Если рассматривать все механизмы повторного использования кода во Vue, а не только mixin, то необходимо упомянуть плагины (как механизмы добавления функциональность на глобальном уровне) и provide / inject (как механизм добавления функциональности в цепочке наследования от родителя к потомкам).
Extends не аналогичен HOC. В extends вы не сможете частично переопределить шаблон (например, сделать обёртку или изменить содержание слота). Вы либо оставляете шаблон такой, какой был у оригинального компонента, либо пишете новый.
Согласен, в случае с шаблонами, с категорией «аналогично» погорячился. Но считаю метод extends более близким к философии Vue с его простотой и ясностью. Что касается непосредственно шаблонов, то для изменения шаблона в расширяющем компоненте, в расширяемом компоненте (компонент заготовка) можно задать шаблон в виде переменной (строка), и в расширяющем компоненте уже переопределись шаблон на основе этой строки, но это уже будет не так явно. А посему, HOC в этом случае (с шаблонами), действительно должен выручить.
Благодарю, ибо дискуссия позволяет глубже вникнуть в суть вопроса! И спасибо за статью, знания никогда не бывают лишними.

HOC — это лишь паттерн. У каждого паттерна есть своя зона применения. Если он не к месту, то он только ухудшит положение. Миксины и extends всё так же полезны, просто в некоторых моментах они проигрывают HOC, а в некоторых выигрывают.

Главное, чтобы патерны не были самоцелью. В смысле, любой ценой использовать их не включая голову, потому что это «модно и круто». Ко всему должен быть критический и рациональный подход.)

Шаблон можно вынести в отдельный файл, написанный на pug или другом шаблонизаторе, поддерживающем наследование/импорт и затем унаследовать его

Я видел этот подход автора статьи. Но у него есть ряд критических недостатков:
1) А если я не использую pug или другой шаблонизатор?
2) Появляется неявная зависимость в коде.

Решение выглядит как большой костыль в попытках расширить непредусмотренную функциональность extend.

Решение не более костыльно, чем использование css-препроцессоров, vuex и Vue router в проекте. Да и почему неявная зависимость, в блоке шаблона шаблонизатор явно указывается.

Просто всё, что нужно сделать — документировать интефейс написанного HOC. Это исключит споры об архитектуре, которая и так тут всем понятна. Документация того, что сделано, не принята в современном WEB-сообществе от слова 'совсем', это пытаются оправдать тем, что всё opensource и ты всегда можешь сам посмотреть код в качестве документации. Это нереально прокачивает в плане развития конечно, но отнимает значительное время от собственно разработки. Тебя как бы принуждают держать в голове сразу несколько проектов.

Три раза прочитал, так и не понял зачем все это. Нужна кнопка с логированием, сделайте компонент назовите LoggerButton и используйте его, там где нужно логирование. Нужно куча всякого функционала, создайте кнопку MyButton, добавьте необходимые св-ва и в соответствии с ними меняйте логику поведения.
Или я не правильно понял поставленной задачи

НЛО прилетело и опубликовало эту надпись здесь

Один компонент MyButton у него свойство settings: {
logger: boolean,
sound: boolean,
popup: boolean
}
И обрабатывайте как вам нужно

И будет километровый компонент, ибо логики сочетаний пропсов в нем будет столько, что устанешь писать.

Чем это хуже кучи непонятных обёрток?

Кому непонятных? Если человек не понимает что это за обертки и что они делают, то портянку кода он тоже вряд ли разберет.
Ну и ниже уже amelekhin написал про принцип единой ответственности. Не должен компонент Button свистеть, пердеть и танцевать вприсядку.
Нужная свистящая кнопка? Оборачивай хоком или делай компонент WhistlingButton.

Мне кажется мы друг друга не правильно поняли, вы предложили, то что я написал в первом комете

НЛО прилетело и опубликовало эту надпись здесь

Это понятно, здесь видимо логирование просто как пример привели, наверное не самый удачный

А если нужен другой компонент со звуком? Ну там панелька какая-нибудь. В этом случае можно переиспользовать HOC и не выдумывать новые компоненты со своими settings. Суть именно в этом. HOC представляет собой независимый слой логики.

"если нужен другой компонент со звуком?"
"не выдумывать новые компоненты"
Так нужен компонент или не нужен?

Нужен один HOC, который добавляет слой логики и сколько угодно компонентов, которые в него оборачиваются или не оборачиваются. Профит в том, что мы один раз написали v-on:click=«playSound» в HOC'е, а не продублировали эту логику в каждом из «звучащих» компонентов. Я не говорю, что это единственно верный сценарий, но в некоторых случаях такой паттерн может быть очень полезен.
Почему попап подтверждения действия должен быть в кнопке, а не в действии? Мне кажется, создать действие confirm и поставить его первым в цепочку действий — логичнее.
UPD: то же касается звука и логирования.
НЛО прилетело и опубликовало эту надпись здесь
Фундаментальная проблема: этот подход предлагает копаться в шаблоне. А шаблон бы лучше лишний раз не трогать.

HOC — это костыль из React, зачем его тащить туда, где даже нет таких архитектурных косяков, ради которых его придумали?

А какие альтернативы HOC'а, которые решают подобные задачи? Миксины в данном случае не подходят, extends тоже. У них своя задача, у HOC своя.
Справедливости ради нужно отметить, что в Реакте HOC'и постепенно переезжают в Hook'и :)
Справедливости ради Evan You экспериментировал с хуками уже (естественно, после релиза от реакта). Даже ядро Vue не пришлось трогать :)
github.com/yyx990803/vue-hooks

Вообще хуки прикольные, но они не заменят HOC на 100%. У них много общих задач, которые они решают, но есть и различия.
У HOC 2 серьезные проблемы.
1) Если на компонент нужно несколько оберток, то, как написали в первом комменте, приходиться долго разбираться, что там происходит, разматывая эти обертки.
2) Нет разделения props.
По хорошему, каждая обертка должна получать только props, с которыми она работает. А компонент, только свои props. В HOC в принципе можно сделать так, но он не требует этого и в нем нет готового механизма для этого. Поэтому в больших проектах с активным использованием HOC получается жесть.

Имхо, Material UI для react и react-admin (от Marmelab) — хорошие примеры того, к чему HOC могут привести.
React devtools показывает с десяток HOC на каждом компоненте. Исходники тоже как-то страшновато выглядят.

В общем, я бы не рекомендовал использовать HOC.
Ден итак своими Redux и HOC реакт подпортил, давайте хоть в Vue не будем тянуть его наработки) Пусть другим путем идет.
НЛО прилетело и опубликовало эту надпись здесь
Кто научит весь мир так делать? Нам в основном приходится работать с библотеками, где так не сделано. Когда приходишь работать на чужой проект, там тоже так не будет сделано.

Ну и в любом случае делают так, что на на вход получаем один набор props, а передается дальше измененный набор props. (Например, в HOC для валидации как минимум добавится prop «прошла ли валидация»).
HOC — это паттерн. Как и с любым другим паттерном, если использовать его бездумно, то он будет только мешать.

Возьмём к примеру паттерн декоратор (на примере любого классического серверного языка, пусть даже PHP). Его концепция позволяет сделать то же самое — обернуть какой-нибудь объект в 10 обёрток, тем самым вызвать wrapped-hell. Но почему-то им до сих пор пользуются и пользуются успешно. Всё потому, что используют его там, где он принесёт много пользы, а не везде.
Нет идеального паттерна, есть хорошие паттерны для решения конкретных проблем.

В HOC в принципе можно сделать так, но он не требует этого и в нем нет готового механизма для этого.

HOC — это частный случай HOF (Higher Order Function). Вся концепция HOC — это получить один (на самом деле можно и несколько, но это особо не важно) компонент параметром и на основе полученных данных выплюнуть другой компонент. В целом вообще без разницы, что за код там будет внутри. Хотите как-то делить props? Пожалуйста, никто не запрещает. Хотите добавить какие-то сложные условия работы? Пожалуйста, никто не запрещает. Хотите с помощью HOC реализовать паттерн Container Component? Опять же, никто не запрещает.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории