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

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

Вместо connect из react-redux можно использовать хуки useDispatch и useSeletector и результат будет тот же самый.

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

А где тут улучшение читаемости ? Элементарная функция стала чем то монструозным.

Логика была отделена от представления и может быть изменена и протестирована отдельно. Элементарная функция здесь для упрощения понимания материала. Так же не стоит забывать, что минусы так же перечислены в конце статьи и если они критичны, то не следует пользоваться предложеным способом.

Статья полезна, ибо описывает один из способов разработки. Расширение кругозора и другой взгляд еще никому не вредили. Пользоваться или нет - каждый решит сам.

С чего вы решили, что эти плюсы существуют не только у вас в голове ?

Например.

Пишем сервер на brainfack

Плюсы

Минимальное колличество символов, при написании кода

Минусы

Усложнение процесса разработки

А потом отдаем этот сервер в поддержку третьему разработчику.

Из жизни.

Мне надо было на реакте за 2-3 дня по постановке раздел приложения собрать.

С вашим подходом я боюсь у меня ушло бы на это раза в 3 4 больше времени.

А поддержка такого кода в сложном приложении может стать адом.

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

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

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

Вы объявляете развязку представления и логики как преимущество, взяв за догму что это хорошо само по себе. Однако нет никаких доказательств что это всегда так (я лично из опыта сказал бы, что это в большинстве случаев совсем даже не так). В данном случае вы обменяли простоту и производительность (а HOC в реакте раздувают VirtualDOM и снижают производительность кода и отладки) на достаточно субъективную "читаемость". Можно было достигнуть почти того же, просто вынося всю логику в большой хук и замокав его для тестов рендеринга. Это намного проще читается и отлаживается, а заодно не создаёт соблазна вынести HOC в отдельный модуль и получить еще оверхед времени выполнения на резолвинг дополнительных модулей. В общем, ваше намерение понятно, но результат скорее отрицательный.

За догму можно взять и то, что писать всё в рендер функции — это хорошо, чтобы было проще. Могут быть разные ситуации, где один подход будет выглядеть привлекательнее другого и наоборот. Но из моего опыта есть закономерность, что написанный код в одном файле после его декомпозиции становится более читабельным. Разделение на логические блоки кода создаёт более четкие границы, которые человеку проще воспринимать. Конечно, могут быть исключения.

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

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

Исходный вариант: есть компонент, который знает как отрендерить интерфейс и какой хук вызвать чтобы отработала бизнес-логика. Делаем себе пометочку, что вообще-то ему не очень стоило бы знать про эту бизнес-логику. Но с прагматической точки зрения проблемы от этого бывают крайне редко. Если вдруг это ПРОБЛЕМА, то можно передавать ссылку на хук как один из пропсов с дефолтным значением. Это попахивает странностями, но поможет динамически менять поведение в широких пределах. Всё, после этого компонент знает какая там бизнес-логика по умолчанию и позволит поменять её при большой необходимости.

Ваш усовершенствованный подход: есть компонент, который знает как отрендерить пропсы. Неплохо. Есть хук, который умеет в нужную бизнес-логику. Тоже неплохо. Но теперь нам нужен способ, чтобы их связать и а) нам по-хорошему надо делать это в другом файле и б) вся ответственность получившегося HOC в вызове хука и другого компонента, то есть просто сшивание простейших вещей. Если возвести подобный подход в ранг корпоративного правила в крупной компании, кодовая база получит по десятку лишних строк и лишний файл на каждый компонентик, плюс необходимость прыгать глазами между файлами. С точки зрения принципа бритвы Оккама -- это излишнее усложнение, так как существует более простой способ.

Полезный комментарий, благодарю.

На это нам намекает хук renderHook для тестирования хуков. Иначе говоря, наш хук привязан к "экосистеме" компонента.

Это же просто метод для тестирования хуков, после "исправления" просто добавилась другая зависимость.
Хуки для того и вводили, чтоб избавится от миксинов и хоков. А тут вместо обычного, понятно всем компонента используется connect чтоб соединить всё, так ещё и надо идти смотреть его доку, чтоб понять, что тут происходит.
Из плюсов, читаемость вещь субъективная, процесс тестирование чет особо проще то и не стал это вот точно, хуки прекрасно повторно используются и без этих костылей.
(Картинка, раньше было лучше)
Опять таки, что делать в этом "магическом" подходе, с хуками которые нужны для UI.
Или почему не выбрать redux-saga оно тут больше подходит в плане декомпозиции.
Немного истории: https://legacy.reactjs.org/docs/hooks-intro.html#motivation

Согласен читаемость вещь субъективная, поэтому некоторые плюсы могут стать минусами для другого разработчика. Тут хотелось сделать акцент на возможности декомпозиции, а инструмент вторичен (+ redux-saga менее популярный вариант). Спасибо за ссылку

Приведите, пожалуйста, реальный пример использования такой возможности декомпозиции, которую нельзя сделать через только хуки

По моим примерам из статьи видно, что сделать можно всё через хуки. Я хотел добиться результата, где в мою логику не протекает что-то связанное с render циклом компонента. Это сделает связь между представлением (отображением) и логикой менее сильной, что не может сделать хук.

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

Прощу прощения, всё чтобы я не привёл в качестве примера не будет сильным аргументом. Так как "сделать можно всё через хуки". Но вот вам утрированный пример представим что надо шарить между командами логику. Команды пишут на разных фреймворках. Сможете ли вы шарить ваши хуки в этом случае?

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

Например, в теории может возникнуть необходимость шарить между командами, работающими на разных фреймоворках, бизнес-логику, а на практике если логика изначально написана под React и хуки, то переиспользовать её скажем в Svelte врагу не пожелаешь.

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

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

Вы правы - логика и представление так плотно свяжутся, что их почти невозможно менять независимо друг от друга.

Но хук так же не защищает от этого. Хук, реализующий сложную бизнес-логику, так же может стать непереиспользуемым, так как будет плотная связь с конкретным отображением.

Хук от этого не спасёт, он просто нужен для реализации. И получается, что в общем случае выносить вызовы хуков в HOC - достаточно безблагодатное занятие. Возможно, есть исключения, но их немного

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

И в чем же проблема оставить один хук и один компонент, который использует этот хук? Если у вас поменяется ViewModel и ее внешний интерфейс, вы в любом случае будете делать какие-то изменения в отображении/передаче параметров. Сейчас же у вас вместо простейшего решения из компонента и хука - слайс redux-а, компонент отображения, компонент, который занимается связью redux-слайса и отображения. Во что это превратится, если вы напишите что-то более серьезное, чем counter, думаю, говорить не нужно.

Я не вижу проблемы. Можно сделать 2 юнита - компонент (отображение) и хук (его view-model). То есть то же самое, что вы предлагаете, но намного проще. Зачем redux запихивать, в чём его преимущество? Да и в принципе, для решения конкретно этой задачи и в подавляющем большинстве задач хватит хуков реакта.

Проблема в том, что я бы хотел видеть чистом виде вёрстку и в чистом виде логику без sideeffects от компонента. Связь конечно будет между ViewModel и внешним интерфейсом, но это не значит, что стоит всё писать в одном месте, наращивая её. Не могу не согласится, что использование redux выглядит чрезмерным, но именно он помог сделать такого рода деление (в данном случае это его единственное преимущество).

Почему в одном месте-то?
2 файла, один для логики, другой для JSX.
Что тогда вы брали инфу из пропсов, что сейчас вы берете инфу из хука в одной строке. Только с хуком у вас нет дополнительного компонента-обертки и redux-а. Всё намного проще и также тестируется. Просто мокаете не пропсы, а хук. И всё. А хук отдельно тестируете. Если это действительно необходимо, потому что всё покрывать тестами тоже плохо (ИМХО).

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

А зачем это нужно? В чем преимущество будет отойти от жизненного цикла компонента?

Будет меньше факторов влияющих на работу логики. Написание хуков накладывает некоторые ограничения. Навскидку

  • Вы не можете менять порядок хуков между перерисовками

  • Где то требуется мемоизация, так как это может сказаться на перерисовке

  • При тестировании надо вызвать перерисовку хука через утилиту act

  • Хуки нельзя вызывать из классовых компонентов или в других местах кроме render функции

Поэтому я искал способ избавится от примеси чего то компонентного в логике, что сделает её более чистой, простой и более переиспользуемой.

Мне одному показалось, что статья на тему "Как сделать из мухи слона"? Хотя может и заблуждаюсь, до конца дочитать не осилил, возможно в конце раскрыли замысел)

Не одному) раздуть так каунтер это надо уметь.

Что правда, то правда ?

Я сейчас работаю над проектом, в котором некоторое время фанатично применялся mobx. Настолько фанатично, что у каждого чекбокса есть свой presenter, store, HOC, который связывает store, presenter и компонент. И там, где можно было написать одну строку кода с useState или коротенького хука useToggle, внезапно появляется целых двадцать, плюс тесты на них. А потом глаза лопаются от монструозности происходящего.

Вообще, по мне, так мораль в том, что надо знать где лучше делать максимально просто и тупо, а где стоит применять разничные паттерны, включая все эти M\s+(P|C) и понимание это приходит с опытом.

Эмм, подождите-ка, у вас в слайсе name: "count". Это значит, что в сторе данные будут храниться по ключу "count", то есть у вас может быть всего один такой элемент. Это именно то, что вы ожидаете? Он вроде универсальный на вид, хотя понимаю, что это просто пример. Обычно в стор кидают данные общие для страницы - состояние футера, там, или свёрнутости панелей.

Я понимаю ваше стремление отделить логику от всего, включая хуки и вообще неприятие хуков. Сам долго упирался, но вариантов особо нет. Возможно, через несколько лет произойдёт очередное переосмысление, а-ля "ООП это сложно, давайте откажемся от классов" и появится что-то другое. Надеюсь, этим уже займётся ИИ. Но и сейчас есть варианты получше.

Во-первых, зачем вам connect()? Он считается устаревающим:

connect still works and is supported in React-Redux 8.x. However, we recommend using the hooks API as the default.

Как выше отметил @devlev, вы можете просто подключать слайс и использовать

const dispatch = useDispatch()
...
dispatch(increment())

Селекторы недавно позволили добавлять прямо внутрь слайсов, что упрощает их использование, связывает в одно целое, но я не очень понял как теперь быть со сложными вложенными селекторами и они, кажется, это ещё не проработали. Да и не очень хочется смешивать разнородный код в одном слайсе, правда, можно вынести в другой файл, только чем это тогда сильно отличается, что так, что так по 2 файла. Но для простых селекторов, просто берущих значение, подойдёт и в слайсе.

Это всё если вам надо хранить значение в общем сторе. Если же компонент универсальный, из Design System, date picker какой-нибудь со своим состоянием, то к стору его точно привязывать не стоит. И тут если хочется разделять логику и отображение, вполне разумно остановиться на уровне разделения типа вашего Bloc с хуками и Display c отображением. Причём не уверен, что нужно хук setCount выделять отдельно просто ради модульности, только если он самодостаточен и используется в разных компонентах. А можно и рендерер не выделять.

Я так понимаю, весь сыр-бор из-за тестов. Тут пойдёт субъективное. А надо ли для всего этого писать юнит-тесты? Во многом это будет тестирование Реакта и простейших действий, чуть ли не арифметики. Я сталкивался с тем, что тесты аж никак не выявляют ошибки, потому что ошибочное значение на автомате оказывается и в ожидаемом и в генерируемом значении. Тесты это очень утомительно и часто всё делается довольно бездумно, на автомате. Эффективность таких тестов сомнительна. По-моему лучше это делать просто наглядными примерами где-то в Сторибуке, ну, там и автоматически можно проверять функционал, но я с этим не разбирался пока. Мне кажется так ошибки выявлять намного реальнее, пусть даже не автоматически. Конечно при рефакторинге придётся визуально оценивать не сломалось ли что-то и пробегаться по примерам. Можно добавить интеграционные тесты на Cypress каком-нибудь. Я вот честно не сталкивался практически с тем, чтобы появлялись ошибки, которые бы обнаруживались такими низкоуровневыми тестами, а труда на их поддержание надо очень много. Только для действительно сложных компонентов, обработчик JSON схем я прописал нечто типа юнит-теста прямо в Сторибуке, так было проще его писать. Но, повторюсь, это субъективно и вашему проекту может не подходить.

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

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

Тесты используются, как пример смешения хука и рендера компонента и для помощи в декомпозиции. Как их писать и что покрывать тестами - другая история.

Надеюсь, я никогда не попаду на проект, после вас))

? Это презентация небольшого исследования - А что если не использовать хуки?. Конечно я не делаю так на боевых проектах. Это лишь желание дать пищу для ума, что можно по другому.

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

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

Мы в одном проекте применяли разделение View/ViewModel через создание хука useViewModel, который клался рядом с компонентом (component.tsx + component.vm.ts). Внутри которого использовались useSelector, useDispatch и другие хуки, и с возвратом текущего стейта и методов для самого компонента.

Мы на проекте делали ровно обратное, с редакса с сагами, хоками, классами и тд переписали всё на рекойл и хуки, всё стало читабельнее, масштабируемее и кода раза в 2 меньше. Смысл пробовать подходы, от которых разработчики фреймворка отказались некоторое время назад?

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

Публикации

Истории