Comments 149
MVC до сих пор остается, пожалуй, лучшим способом разработки серверной части приложений.
Не согласен с этим утверждением. Конечно, MVC можно притянуть за уши к чему угодно, но я предпочитаю работать с концепцией middleware pipeline, которая полностью устраняет старые монструозные контроллеры из кода, заменяя их на узкоспециализированные сервисы.
Толстые контроллеры, как и толстые модели — это зачастую ошибка проектирования.
Излишняя толстота любого из слоев — это плохо. Но бизнес логике место именно в моделях. Это да. Контроллеры должны быть максимально тонкими.
Бизнес логика должна лежать в сервисах, а модели лишь проецируют данные приложения в БД.
И да, модели не обязаны быть построены из БД.
У меня они могут строиться из конфигурационных файлов и много чего еще.
MVC не серебряная пуля, если какие-то суровые проблемы — можно и посмотреть на другие шаблоны.
Если Вам попадался не лучший код — то это вовсе не означает, что патерн плохой.
Это примерно, как уроки по GoLang — меня от изучения него очень долго отталкивал стиль написания кода в примерах. Потом сам для себя решил, что если в примере экономят на буквах(в именах переменных, например), то никто не мешает мне на нем писать нормально.
Идея в том, что не нужно смешивать в модели задачи хранения и обработки.
Бизнес-логика, как правильно заметили, должна находиться в отдельном слое Services, независимом от контроллера и от реализации модели. Более того, в махровом энтерпрайзе бывает, что бизнес-правила верхнего уровня вообще выдаются неким отдельным сервисом масштаба корпорации на некотором формализованном языке.
Есть альтернативное мнение, что модель должна быть исключительно тупой и в идеале вообще генериться автоматом на базе некого формального описания структуры данных.
Так это и будут голые данные, зачем тут еще что-то «генерить»? (кроме как чисто какие-либо объекты ORM или вроде того — но это уже детали реализации).
Придерживаюсь точки зрения, что модель — это данные плюс поведение (бизнес-логика), а не только данные.
В реальном мире структура моделей вам приходит извне (от железа, от другого софта, в т.ч. унаследованного, из стандартов, от гос. органов и пр.), а правила их обработки (бизнес-логика) — тоже извне, но из другого.
Примеры.
В бизнесе: модели заданы альбомом бухгалтерских документов, утвержденным минфином, бизнес-логика — приказами налоговой, которая любит каждый год в них чего-нибудь менять.
В инженерке: модели заданы а) софтом типа солидворкса или акада, файлы которого нужно уметь не только читать, но и корректно писать б) промышленными железками с их крайне тупыми форматами. «Бизнес-логика» приходит в виде ТЗ, ТУ, ОСТов и т.д. и т.п., а также стопки увесистых книжек про матан, сопромат и прочую жуть.
В геймдизе: форматы моделей заданы видеокартами и используемой либой. Да, однородные координаты, да, только вот такой формат текстур. «Бизнес-логика» придумывается на ходу геймдизом и меняется семь раз в неделю.
Вы конечно можете запихать в модель «годовой баланс» правила его сведения, или в модель космического корабля — правила расчёта урона его лазеров в зависимости от цели… но это очень плохая идея.
Почему-то многие считают, что модель — это классы в папочке models.
Модель — это не класс и не объект. M в MVC — это самое абстрактное определение модели из существующих.
Модель — это источники данных и правила их обработки, а работа приложения — моделирование.
Объект, хранящий космический корабль — часть модели. Расчёт лазеров в зависимости от цели — тоже часть модели. Какой-то там сервис для обработки объектов — тоже часть модели.
middleware pipeline
Можете расказать как устраняются старые монструозные контроллеры?
В результате то, что можно ещё назвать контроллером по аналогии с ранее используемыми в Symfony 2 или ZF2, выглядит гораздо тоньше и является просто ещё одним шагом в цепочке логики.
Подождите-подождите, но у вас же мидлварь выполняет только инфраструктурные задачи, нет?
Они заменяются на цепочку из middleware, например как здесь
Если вы вынесли код из контроллера в отдельный файл, как мусорку — этот код никуда не делся. Зачем логику разных частей сбрасывать в одном файле? Те мидлвары, которые вы указали — глубокие детали реализации и в роутах им точно не место. А если вынести в контроллер (где им и место), то будет самая обычная композиция.
Примечание: меня до сих пор преследуют кошмары после ревью проектов, в которых одновременно использовались React и Angular 1.x.
А можно поподробнее про этот момент? Что вызывало такие кошмары.
Мы сейчас подумываем дать возможность интегрировать в свои проекты реакт-компоненты. Не скорости ради, как было в Ангулар 1, а скорее для более широкого выбора возможностей при разработке отдельных частей.
Да здравствует jQuery! :)
- js
- sass
- templates
В свою очередь js разбит на пакеты и модули, к примеру
- menu / header
- dashboard / owner
А каждый модуль состоит из router, handlers, views etc
PS handler — это аналог контроллера
Когда читаю подобные статьи и бывает думаю, а может поменять что-то, сменить структуру. Но когда начинаю анализировать, то не могу придумать что либо лучшее, чем то что есть сейчас.
Модульность, которую вы упомянули, безусловно выручает, причем независимо от архитектуры приложения. Грамотное структурирование кода на модули всегда в почете.
Для больших проектов разбиение на js,sass, template это зло, потом тяжело найти части, я разбиваю по модулям/компонентам, каждый со своими скриптами, стилями и ресурсами. Ну а webpack уже все в одно место собирает. Так можно сосредоточиться на одном маленьком и независимом модуле/компоненте и не нужно искать его запчасти в разных местах.
Абсолютно согласен. Если templates положить отдельно от js, то иногда получаются странные переходы.
Из гипотетического src/js/app/users/UserView.js нужно делать такой require, чтобы получить соотвествующий шаблон
require('../../../templates/app/users/UserTemplate.hbs')
А если потом понадобится переместить js-файл, то нужно обязательно не забыть и про шаблон.
Минус такого подхода — размер модуля немного увеличивается, но не настолько что бы бить тревогу.
По поводу копирования — редкий кейс. Так как основные модули собраны в core и запаблишены в локальный NPM репозитарий. Другие проекты, которые разрабатываются просто инсталят себе модули. Таким путем достигается и версионность и поддержка.
К тому же MVC был придуман для оконных программ с событийным поведением пользователя — тот же фронт.
Как по мне, то react/redux как раз ложатся на классический MVC, где реакт — вью, редакс — модель, а реакт-редакс, контроллер, обеспечивающий реакцию вью на изменения модели.
Та все 200%, но свидетелям пришествия реакта как горохом об стену. Автор редакса даже, чтобы побольше лапши своей пастве навешать ввел для знакомых слов такие понятия:
My presentational components:
— Are concerned with how things look.
My container components:
— Are concerned with how things work.
Ничего не напоминает? Поняли теперь как современные разработчики по-модному называют вьюху и контроллер? Presentational and Container!
А разве архитектура с однонаправленными потоками — это не MVC? Ведь именно MVC постулировала зависимости в одном направлении.
На примере Реакта:
Model -> application state,
View -> JSX
Controller -> Reducer + Middleware, разделение на логику перехода по состояниям и логику взаимодействия с "внешним миром".
А к тому моменту все популярные серверные фреймворки в том или ином виде реализовывали классический MVC
Дальше можно не читать, в принципе. На сервере никогда не было классического MVC, на сервере был Model 2 и его наследники. Для классического MVC нужны события, которые в парадигме запрос-ответ сделать сложновато.
https://habrahabr.ru/post/321050/
MVC может быть активным/пассивным, поэтому событий в нем может и не быть.
А так я согласен с предыдущим комментарием. Однонаправленный поток — этот тот же самый MVC. Ничего нового не придумали.
Что на самом деле произошло?
1. работа со стейтом в одном месте помогает уменьшить разные сайд эффекты из-за мутабельности данных
2. отказались от точечной перерисовки — да какой смысл это делать, когда браузеры стали невероятно быстрыми, а большая часть компонентов очень маленькие. Бери и перерисовывай все целиком и никто ничего и не заметит.
3. мы снова получили событийную модель, на которую очень ругались в бекбоне. Говорили о том, как это сложно поддерживать, непонятно что происходит и тд. Но теперь она стала всем проще и понятнее. На самом деле мы просто уже привыкли к событиям и натерпелись связанного лапшекода и трудноподдерживаемого состояния. По факту чем меньше ваш компонент, тем проще подписываться на события, которые могут на него повлиять. Все это же самое можно делать сейчас и в бекбоне.
== Для классического MVC нужны события,
откуда вы это взяли?
== которые в парадигме запрос-ответ сделать сложновато.
причём здесь "запрос-ответ"?
откуда вы это взяли?
Из описания. Observer построен на событиях, даже если их нет явно в языке.
причём здесь "запрос-ответ"?
При том, что HTTP — это запрос-ответ. Соответственно, веб-сервера (как минимум преимущественно) — запрос-ответ. Соответственно, серверные MVC-фреймворки (в подавляющем большинстве) — запрос-ответ.
Это фантазии мастера выдуманных архитектур, прославившегося циничной эксплуатацией тупости ООП-шников.
Вебсокеты есть везде и давно. В отличие от "классического MVC"
Это фантазии мастера выдуманных архитектур, прославившегося циничной эксплуатацией тупости ООП-шников.
Says who, простите?
Вебсокеты есть везде и давно. В отличие от "классического MVC"
Ну так покажите мне (веб-)серверный MVC-фреймворк на вебсокетах.
тот, кто читал эту статью, а так же ряд других работ данного автора.
Я не видел и мне не нужно. Вы утверждали, что "классическому MVC" нужны события, которые "в парадигме запрос-ответ сделать сложновато". Вебсокеты — пример технологии, позволяющей делать события в веб. Вот вы и покажите "классический MVC" на их основе
тот, кто читал эту статью, а так же ряд других работ данного автора.
Я тоже читал, и не говорю такого. Мнение на мнение.
Вы утверждали, что "классическому MVC" нужны события, которые "в парадигме запрос-ответ сделать сложновато". Вебсокеты — пример технологии, позволяющей делать события в веб. Вот вы и покажите "классический MVC" на их основе
Эмм, у вас с логикой что-то. Я утверждаю, что в серверных фреймворках не классический MVC, а Model 2. Совершенно не вижу, почему я должен показывать классический MVC на основе событий на основе веб-сокетов.
Если бы автор описал применяемую где-то архитектуру, написал бы хоть строчку кода в продакшн или стандарт, или просто сделал что-то полезное для индустрии, я бы с вами согласился
Да, целых три предложения, очень сложно думать их одновременно. В любом уважающем себя серверном фреймворке есть вебсокеты. Следовательно там есть события. Которые согласно вашему высказыванию необходимы для некоего "классического MVC". Вот я и прошу вас показать этот "классический MVC"
Если бы автор описал применяемую где-то архитектуру, написал бы хоть строчку кода в продакшн или стандарт, или просто сделал что-то полезное для индустрии, я бы с вами согласился
Вы имеете полное право думать об автора что угодно, равно как и не соглашаться со мной. К счастью, я имею такое же право думать о вашем мнении что угодно и поступать с ним так, как я считаю нужным.
В любом уважающем себя серверном фреймворке есть вебсокеты.
Я боюсь, что начиная с этого момента мы уже разойдемся в определении "уважающего себя серверного фреймворка".
Следовательно там есть события.
Нет, следовательно, там есть возможность реализовать события.
Вот я и прошу вас показать этот "классический MVC"
Я и говорю: ошибка в логике. Вы просите меня показать то, чего, согласно моему утверждению, нет. Поэтому нет, не покажу.
Ну и да, примера ради: в ASP.net MVC нет вебсокетов, нет событий (кроме жизненного цикла самой инфраструктуры) и нет классического MVC — там типичный Model 2.
Здесь пойнт не в субъективности, а в оценке автора, на которого вы сослались как на авторитет — если он не создал ничего практически ценного, то какие есть основания у кого либо вообще принимать его мнение во внимание?
Я боюсь, что начиная с этого момента мы уже разойдемся в определении "уважающего себя серверного фреймворка".
зависит от задач конечно. Для SPA серверный фреймворк, в котором нельзя безболезненно прикрутить вебсокеты следует выкинуть на помойку. и не надо пожалуйста рассказывать, что это субъективное мнение
Нет, следовательно, там есть возможность реализовать события.
вебсокет без реализации событий лишён смысла
Я и говорю: ошибка в логике
Вообще-то вы предложили мне доказать негативно сформулированный тезис, логически следующий из ваших утверждений "Ну так покажите мне (веб-)серверный MVC-фреймворк на вебсокетах", что не логично. На что я естественно предложил вам сделать это самому.
в ASP.net MVC нет вебсокетов
В контексте разговора существенно, что там есть возможность использовать вебсокеты, пусть даже через костыли. Это больше говорит о качестве инструментов .net/C#, чем о предмете разговора — многое, из того что нужно, отсутствует, зато дофига лишнего
нет классического MVC
Не надо выдавать ваши с Фаулером домыслы о том, что есть классический MVC, за стандартные общепринятые понятия
Здесь пойнт не в субъективности, а в оценке автора, на которого вы сослались как на авторитет — если он не создал ничего практически ценного, то какие есть основания у кого либо вообще принимать его мнение во внимание?
Ваша оценка "не создал ничего практически ценного" — субъективна. Поэтому вы, конечно, можете не принимать его мнение во внимание. А я могу не принимать во внимание ваше мнение.
Для SPA серверный фреймворк, в котором нельзя безболезненно прикрутить вебсокеты следует выкинуть на помойку.
Вот только серверное MVC не использовалось для SPA (его можно использовать, понятное дело, но это будет смешанный фреймворка уже).
В контексте разговора существенно, что там есть возможность использовать вебсокеты, пусть даже через костыли.
Есть, но там все равно останется Model 2.
Не надо выдавать ваши с Фаулером домыслы о том, что есть классический MVC, за стандартные общепринятые понятия
Осталось понять, кто же определяет понятие "классический MVC".
(мне все-таки интересно, с чем именно вы спорите — с используемым мной определением классического MVC, или с тем, что классический MVC в этом определении не использовался в серверных фреймворках?)
Ваша оценка "не создал ничего практически ценного" — субъективна.
Ваше игнорирование аргументации не прокатывает, я выше написал почему это объективно
Вот только серверное MVC не использовалось для SPA (его можно использовать, понятное дело, но это будет смешанный фреймворка уже).
Именно. MVC с SPA использовали в силу того, что людям свойственно ошибаться, а мозги отравлены фаулерами. А так MVC на сервере такое же легаси, как и MPA, и дорога ему в ту же помойку
Осталось понять, кто же определяет понятие "классический MVC".
мне все-таки интересно, с чем именно вы спорите
никакого "классического MVC" не существует, концепция MVC слишком размытая и абстрактная. Определения, область применения и пределы распространения не ясны. Если обобщать всё, что про это известно, так это будет скорее RoR, Django и ASP.net mvc со всем комплексом их уродства, нежели чем фантазии ООПшных теоретиков. Веб фрэймворки дают опыт обеих знаков, Фаулер и иже с ним не дают ничего, а потому идут далеко. То у них UML, то Rich Model, то DDD, то какое-то особенно правильное "классическое" понимание MVC то еще какая-то хрень.
я выше написал почему это объективно
Nope. Нет объективного способа оценить "вклад в индустрию". Вы, видимо, не считаете Refactoring и PoEAA "вкладом", я — считаю, "и вместе им не сойтись".
никакого "классического MVC" не существует
Ну вот видите. Значит, и на сервере его не существует. С чем спорили-то?
Если обобщать всё, что про это известно, так это будет скорее RoR, Django и ASP.net mvc со всем комплексом их уродства
И на основании чего вы делаете такой вывод?
Определения, область применения и пределы распространения не ясны.
Ну, если определения игнорировать, то они и не будут ясны.
То у них UML, то Rich Model, то DDD, то какое-то особенно правильное "классическое" понимание MVC
… а кто-то где-то говорил, что оно "особенно правильное"?
Нет объективного способа оценить "вклад в индустрию"
какой наивный релятивизм, простите) можно тысячи бесспорных примеров вклада в индустрию привести. Неподтверждённая практикой гум. теория, на которую вы ссылаетесь, к ним не относится
И на основании чего вы делаете такой вывод?
Из того, что имеет хоть какой-то практический смысл, это наиболее близко к изначальной концепции MVC Реенскауга
Ну, если определения игнорировать, то они и не будут ясны.
Можно игнорировать, можно не — это не имеет значения, как и для всего, что не подтверждено практикой
можно тысячи бесспорных примеров вклада в индустрию привести
Попробуйте. Но в любом случае они не будут подтверждать то, что любой другой вклад в индустрию можно объективно оценить.
Из того, что имеет хоть какой-то практический смысл, это наиболее близко к изначальной концепции MVC Реенскауга
О, то есть вы все-таки сравниваете с Реенскаугом. Остается вопрос (а) как именно вы сравниваете и (б) как вы определяете, что имеет практический смысл, а что нет.
Но не суть, вы же уже сказали, что "классического MVC нет".
Можно игнорировать, можно не — это не имеет значения, как и для всего, что не подтверждено практикой
В таком случае этот спор изначально бессмысленнен. Я и не сомневался.
RoR, Django и ASP.net mvc
А ничего, что все три фреймворка построены на паттернах из PoEAA?
Ну, наверное, все-таки стоит ознакомиться с источником перед тем как его критиковать.
Вообще всё построено на паттернах. Elm Architecture, Erlang/OTP например — квинтэссенция применения всевозможных паттернов. В то же время ничего не построено на паттернах. Такая вот особенность применения гуманитарных знаний, к которым относятся паттерны, в технических областях. Знакомится со всеми книжками по паттернам нет смысла. Можно прочитать один раз одну книжку чисто чтобы словарь понимать, и забыть про ООП-литературу насовсем.
Для SPA серверный фреймворк, в котором нельзя безболезненно прикрутить вебсокеты следует выкинуть на помойку. и не надо пожалуйста рассказывать, что это субъективное мнение
Конечно субъективное, причём и неверное. Термин SPA появился в начале 2000-х, вебсокеты — через 10 лет. 10 лет надо было выкидывать фреймворки и писать всё с ноля?
Через 5 лет будут веб компоненты. Встроят ли в них реакт? К сожалению, да
Блин, да откуда берутся эти люди, утверждающие, что MVC не однонаправленный? Разница между MVC и Flux лишь в том, как взаимодействуют контроллер и модель. В MVC контроллер напрямую ковыряется в модели, в Flux он лишь генерит событие с данными, а модель уже сама решает как эти данные разложить по себе. Где тут изменение потока?
Меня больше удивляет термин "однонаправленный поток данных", который не может быть однонаправленным в принципе, если речь идёт о пользовательских интерфейсах. События от пользователя — такой же поток данных, как и события от сервера.
А то, что продвигает фейсбук — антипаттерн "перескакивание через слои", когда глубоко вложенный слой взаимодействует напрямую с корневым слоем, а промежуточные слои не имеют над вложенными слоями полного контроля (контролируют они только один поток, ага).
Так что это не "однонаправленный поток данных", а "контролируемый лишь в одном направлении поток данных". И придуман он был не потому, что это какая-то вся из себя классная архитектура, где корневой контроллер (который обозвали actions) знает о каждой кнопке на странице, а потому, что сначала запилили кривой шаблонизатор (реакт), не способный полноценно абстрагировать внешние компоненты, от внутренних, а потом стали думать, как бы прикрутить к нему обратный поток. И ничего умнее не придумали, как забить на все слои, стучаться напрямую в корневой и копипастить эти бесконечные редьюсеры+константы+экшены на каждый чих.
Меня больше удивляет термин "однонаправленный поток данных", который не может быть однонаправленным в принципе, если речь идёт о пользовательских интерфейсах. События от пользователя — такой же поток данных, как и события от сервера.
В том-то и суть, что на входе приложения события (независимо от источника: события UI, сервера, всякие таймеры и интервалы, иногда даже какие-то аппаратные события), изменяющие состояние приложения, а на выходе отрендеренный интерфейс. Из рендерера события не генерируются, состояние не изменяется. Всё что умеет делать приложение в устоявшемся режиме — обрабатывать внешние события, сначала реагируя на них изменением состояния, а потом реагируя на изменение состояния ререндирингом интерфейса.
Нет, суть как раз в том, что пользовательские события, начинают свой путь от того элемента с которым взаимодействовал пользователь, а заканчиваются изменениями в сторе/субд. Только после этого инициируется поток данных в обратном направлении, который одинаков как в случае пользовательских, так и серверных событий. От того, что фейсбук редуцировал поток данных от пользователя к стору до двух звеньев, не делает его отсутствующим.
Нет никакого обратного потока данных в рамках приложения. Пользователь взаимодействует с элементом UI в целом вне приложения, равно как и сервер и т. п., событие генерирует платформа (например, браузер) и сообщает о нём приложению. Обработчик события в приложении изменяет состояние приложения, и в ответ на изменение состояния приложение ререндерит интерфейс средствами платформы и на этом всё. Один поток: событие платформы -> обработчик в приложении -> изменения состояние приложения -> рендерер приложения -> рендерер платформы. Какие-то события платформы могут не изменять состояния приложения, какие-то обрабатываться самой платформой, какие-то изменять состояние, но это изменение не нуждается в рендеринге, что-то отрендеренное приложением, может не рендериться платформой (например часть окна за пределами вьюпорта), в общем поток обрубается раньше чем доходит до непосредственного аппаратного рендеринга, но вспять он не обращается, единственный способ как бы повернуть его вспять — инициировать новое событие через платформу или сэмулировать его. Часто, конечно, технически есть способы, например, изменить
состояние из рендерера приложения, минуя основной поток, но это грубый выход за рамки архитектуры, которые не должен пройти ревью.
Нет никакого обратного потока данных в рамках приложения. Пользователь взаимодействует с элементом UI в целом вне приложения
Ага, через libastral.
Далее вы описываете типичное монолитное приложение, без какой-либо компонентной декомпозиции, которое невозможно масштабировать без мозолей на пальцах, да кодогенераторов. Задача компонента — инкапсулировать в с бе сложность, предоставляя простой интерфейс его управления. А во флоу, всё внутреннее состояние (на которое внешнему коду должно быть плевать), выпячивается наружу и требует аккуратной копипасты.
Через платформу, тот же браузер, которая частью приложения не является.
Никто не мешает вам разбить свое приложение на компоненты, которые будут практически являться мини-приложениями в рамках того же флоу изнутри, и как бы элементами платформы снаружи. Классический пример для реакта — компоненты форм. Изнутри компонент выглядит как полноценное приложение со своим богатым состоянием, возможно даже со своими потоками взаимодействия с внешним миром, о которых приложение в целом не знает, а снаружи практически ничем не отличается от контрола платформы типа одиночного инпута, кроме разве того, что его value не скаляр, а объект со сколь угодно сложной структурой.
Вынесению состояния из компонента на уровень (или несколько) выше до первого общего вплоть до корневого компонента приложения подлежат только те состояния, которые шарятся между разными компонентами.
Только вот какие состояния шарить решать может лишь компонент-владелец. Поэтому для обеспечения максимальной гибкости из компонента выносятся вообще все состояния, делая их "чистыми". Кривая архитектура порождает кривые решения. Для сравнения, в $mol любой компонент является вещью в себе, но компонент владелец через биндинги может управлять любым его состоянием.
Только вот какие состояния шарить решать может лишь компонент-владелец.
И это логично. Хуже нет, когда компонент "думает": "это мои данные", а по факту их меняют все кому не лень, его даже не уведомляя.
Поэтому для обеспечения максимальной гибкости из компонента выносятся вообще все состояния, делая их "чистыми"
Есть такой подход, но где-то на пути от компонента до глобального неймспейса (включительно) ставится контейнер с единственным/основным назначением держать стейт и передавать нужные его части в нижележащие компоненты через свойства, иногда через контекст. Это может быть как выделенныей компонент, так и какой-нибудь HOC.
Как я понимаю, ваши примеры относятся прежде всего к Redux в качестве хранилища глобального состояния. Он не идеален, не для всяких приложений он даёт ощутимую пользу, а для некоторых только вред приносит.
Альтернатив много, выбирать нужно по требованиям и просто вкусу даже, если по требованиям особой разницы нет. Я сейчас вовсю юзаю обсерваблы MobX и как сторы глобальных или близких к тому состояний, и как стейт отдельного компонента. Главный плюс субъективный — легко применимы обычные принципы ООП с вкраплениями ФП в рамках декомпозиции по DDD.
Подходы к декомпозиции redux-based приложений отличаются от "классических", унаследованных от jQuery-based приложений.
Проблемы с декомпозицией обычно начинаются, когда пытаются эмулировать стейтфулл компоненты на базе редакса.
В редаксе точкой декомпозиции являются именно sub-state + actions + middleware. Это делается довольно неудобно, и ни в какое сравнение не идет, конечно, с "классическими" компонентами, особенно с непривычки. В первую очередь это связано с тем, что точка монтирования зашита в коде middleware и mapStateToXXX.
В редаксе отдельно переиспользуется логика и отдельно переиспользуется UI (в виде dumb components). Ну и, соответсвенно, контейнеры пишем каждый раз.
Но сила редакса в том, что он "развязывает" связь один-к-одному между данными и структурой UI. Он легко позволяет отображать одни и те же данные в нескольких видах, без дублирования кода.
UI вообще часто нелогичен с точки зрения иерархии, часто верхние уровни зависят от нижних, например, добавляются пункты меню в зависимости от выделенной строки в гриде. Редакс это позволяет делать относительно легко (послать экшен можно из любого места), на "классических" компонентах это сделать красиво невозможно без введения аналога сообщений.
Редьюсеры редко переиспользуются потому, что это обычно часть логики приложения (а не компонента). Поведение именно компонента обычно делается внутренним состоянием этого компонента (и его довольно мало, если разобраться).
Но это все-равно отличается от классических компонентов, где смешана логика приложения и логика компонента. Особенно это видно на сложных компонентах типа гридов, которые на реакте и редаксе теряют 80% своей сложности.
Я не смотрел и не использовал именно redux-forms, и, честно говоря, не совсем понимаю, какую выгоду они несут и что именно автоматизируют.
$my_app $mol_book
sub /
<= Menu $my_menu
items <= menu_items /
<= Grid $my_grid
rows <= grid_rows /
selected_row => grid_row_selected
namespace $.$mol { export class $my_app extends $.$my_app {
menu_items() {
const selected = this.grid_row_selected()
if( !selected ) return []
return [
this.Menu().Copy() ,
selected.removable() ? this.Menu().Remove() : null ,
this.canEdit( selected ) ? this.Menu().Edit() : null ,
]
}
} }
Тут создаётся приложение с 2 панелями: грид и меню. В грид проталкивается список строк, вытягивается выделенная строка, на её основе формируется список пунктов меню взятых из меню, и проталкивается обратно в меню.
Не могли бы Вы чуть раскрыть про неспособность реакта "полноценно абстрагировать внешние компоненты, от внутренних"? Я пока что не встречал вразумительных доводов в пользу хранения всего стэйта в отдельной от реакта корневой подсистеме. Вот для меня и мне подобных, привыкших к дектопу, Redux выглядит как абсолютный архитектурный костыль и вообще концептуально чуждая реакту штука с его componentDidMount, setState, и т.п.
Банальный пример — таббар. Как правило состояние "какой таб открыт" остальное приложение не интересует и можно было бы сделать его внутренним. Но иногда нужно им управлять извне и соответсвенно его выносят во вне, по всем заветам flux — кидается событие, где-то ловится, как-то обрабатывается. В результате получается, что даже в случаях, когда нам не нужно управлять открытым табом, мы при использовании такого компонента должны обеспечить его правильным окружением, реализующим работы с его внутренним состоянием.
Как это делается с нормальными компонентами: создаём компонент, который имеет собственное состояние. Если родителю нужно управлять этим состоянием — он просто биндит его на собственное состояние. В результате — оба компонента работают совместно с одним состоянием. А вложенному компоненту не надо знать про окружение.
Если родителю нужно управлять этим состоянием — он просто биндит его на собственное состояние. В результате — оба компонента работают совместно с одним состоянием. А вложенному компоненту не надо знать про окружение.
Плохой подход. Родитель изменяет состояние вложенного, а вложенный об этом и не догадывается. Нарушение инкапсуляции в общем. Нужное родителю состояние должно быть в родителе и передаваться вложенному для отображения, не более. Обратная свзяь от вложенного к родителю через передачу вложенному методов родительского. И да, состояние родителя не равно глобальному состоянию, состояние поднимается ровно до того уровня вложенности, где есть кто-то кому оно нужно, до самого нижнего общего компонента, его меняющего, не выше.
Не лишаемся. Таббар будет принимать из родителя нужный родителю таб как значение по умолчанию (не передали — принимает свое значение), а рапортовать ему не о клике, а об изменении пользователем текущего таба как рапортует тот же селект, которым таббар по сути и является.
А если надо не значением по умолчанию управлять, а текущим значением?
Пользователь кликнул по третьей вкладке, а показать ему вторую? Можно и так, но тогда ответственность таббара только в том, чтобы показывать выбранную родителем вкладку и рапортовать ему о попытке изменения пользователем. Можно совместить в компоненте, если свойство текущая вкладка не указано родителем, то управлять самому, если указано то отдать родителю, если указано то рапортовать только о попытке изменения, если нет — сообщать об изменении.
Плохой подход. Родитель изменяет состояние вложенного, а вложенный об этом и не догадывается.
Обоснуйте. Зачем вложенному компоненту знать где хранится его состояние?
Нарушение инкапсуляции в общем.
Не нарушается, ведь все свойства — публичные.
Чтобы среагировать должным образом на его изменение и "доложить" о нём тем компонентам (как родительским так и дочерним) об изменении если те в нём заинтересованы.
То есть просто изначально нарушенная :) Если всё всем публично доступно и все всё о структуре состояний знают на всех уровнях, то вообще нет смысла городить компонентную архитектуру. Компоненты предполагают изоляцию, при которой могут иметь свое полностью скрытое от родителей состояние либо не иметь его вовсе, а детям только явно передавать его часть.
Реактивное программирование избавляет от этой головной боли.
Кому надо — знают. Кому не надо — не знают. От того, что вы изолируете некоторое состояние, не предоставив к нему публичного апи, — лучше никому не станет.
И не путайте изоляцию (нет доступа из вне) и инкапсуляцию (могу не знать о внутренностях).
Так я о нём и говорю. Компонент хранит своё состояние и реактивно реагирует на его изменение через свое публичное апи путём перерендеринга себя и изменения состояния вложенных в него компонентов по их публичному апи. Как конкретно реализована реактивность — исключительное дело самого компонента.
Станет. Прежде всего компоненту.
Не путаю. Изоляция не обязательна должна быть физической. Можно бить по рукам, разработчикам, которые лезут в состояние не через публичное апи.
Основная польза реактивности — во взаимодействии различных частей приложения. От реактивности в рамках одного приложения мало пользы.
Обоснуйте.
Так не делайте непубличного апи. Это владельцу решать насколько детально он хочет контролировать своё имущество.
Так и взаимодействуют разные части приложения. Компонент хранит свое состояние и реактивно реагирует на его изменения как перевычисляя рендер свой и вложенных компонентов, так и дергая методы своего контейнера.
Компонент может выбрать любой способ контроля за этим состоянием, не переживая что кто-то его изменит снаружи компонента без ведома самого компонента.
"Вассал моего вассала не мой вассал". Контейнер контролирует вложенный в него элемент только в рамках, в которых компонент это позволяет делать, только через его публичное апи. Компонент имеет полное право скрывать и от контейнера, и от вложенных всё, что считает нужным. Вернее даже не скрывать имеет право, а публикует для них всё, что считает нужным и не битом больше.
Поправлюсь: От реактивности в рамках одного компонента мало пользы.
Незачем в каждом компоненте реализовывать свою реактивность. Разве что если вы хотите всех запутать. То, о чём я рассказываю позволяет не переживать, что кто-то снаружи что-то изменит без ведома, ибо логика строится так, что это становится не важно.
Вы опять идеализируете. На практике необходимо кастомизировать компоненты даже в тех, аспектах, которые не задумывались его создателем. А раз мы не можем это побороть, то лучшее решение — возглавить.
Не в рамках одного. В рамках компонента его состояние, на изменение которого он реактивно (для внешнего наблюдателя) реагирует. Что у него под капотом — его личное дело, экспозит наружу он только то, что считает нужным.
Способов реализовать реактивность множество и вовсе незачем стремиться реализовать её во всём приложении одним способом. Нужно брать лучше подходящий для конкретного компонента, а то и брать тот, который предоставляет разработчик компонента, даже не заглядывая под капот. Вот есть какой-то компонент, например, автодополнения — какая мне разница как в нем реализована реактивность, если он ведет себя реактивно? Ну пока не окажется, что он слишком много ресурсов потребляет, конечно. Что обычно бывает, когда реактивность реализуется с помощью сторонних библиотек типа RxJs или MobX, есть и на Redux реактивность.
Не можете кастомизировать предусмотренными способами — сделайте форк и добавьте способов. Не требуйте от создателей компонентов раскрывать всю их кухню наружу, особенно с правом на изменение состояния.
Разные модели реактивности плохо стыкуются друг с другом. Реактивность — это не просто библиотека, которую каждый прикручивает, какую хочет, а способ построения архитектуры.
Так вот, ответьте мне на простой вопрос: зачем использовать стримы, когда есть атомы? Я не вижу пользы от этого химеризма.
Ну и чего мы добьёмся этими форками? Сделаем вид, что не влезли во внутренности стороннего компонента и не огребём проблем с обновлениями? Мастурбация какая-то.
А компонент — способ изоляции локальных решений от глобальных. Не надо стыковать библиотеки друг с другом. Компоненты стыкуются с библиотеками и друг с другом, а что у них под капотом другие компоненты волновать не должно. Это суть компонентной архитектуры — локальные для компонента решения не должны оказывать влияния на всё приложение. Это с одной стороны. С другой — компонент имеет право рассчитывать, что в его внутренности никто не лезет, минуя публичное апи. Есть стандартный способ общения компонентов друг с другом на уровне принятой в проекте архитектуры — его и надо использовать.
Зачем-то надо людям. Я сам предпочитаю атомарный MobX, но если что-то нужное мне использует под капотом RxJs или, прости господи, Redux для реализации реактивности, то меня это волнует только в плане объёма зависимостей.
Обычный путь развития опен-сорс продуктов.
Не надо, не должно, не должны и надо
Однако, если не поддаваться догматизму, то вдруг выясняется, что:
- компоненты получаются крайне простыми
- компоненты легко стыкуются друг с другом
- компоненты могут быть детально настроены под задачу
- компоненты очень легко тестировать
- это не доставляет никакой боли
Достигается это за счёт того, что компонент — это не просто какая-то чёрная коробочка, а крайне простая штука:
Есть свойство "состояние вьюшки" — вы можете перегрузить сразу его, но это не очень удобно. Это свойство по умолчанию реализует вычисление себя из набора других свойств. Вы можете перегружать их, но и это не всегда удобно. Поэтому их тоже можно реализовать так, чтобы по умолчанию они вычислялись через другие свойства. И так далее. Вся логика компонента таким образом — это реализация по умолчанию. И любое свойство может быть перегружено другой реализацией, что благодаря реактивности подхватят и остальные.
Зачем-то надо людям.
Ну так, React не предоставляет никакой реактивности, вот люди и лепят кто во что горазд. Потребность есть, стандарта нет.
Обычный путь развития опен-сорс продуктов.
Это если бэкпортить изменения. Если не убедите мейнтейнера, что ваши изменения нужны всем, то придётся до конца жизни с запозданием и продираясь через кучу конфликтов затягивать изменения из основной ветки. Субклассинг тут и проще будет и проблем меньше вызовет.
Достигается это за счёт того, что компонент — это не просто какая-то чёрная коробочка, а крайне простая штука:
Как по мне, то может снаружи она и простая, но писать код так, зная что всё API у тебя публичное гораздо сложнее, чем если хотя бы словах сообщил пользователям своего кода "это моё, сюда не то что писать, а даже читать это нельзя, потому что в любой момент могу удалить"
Ну так, React не предоставляет никакой реактивности, вот люди и лепят кто во что горазд. Потребность есть, стандарта нет.
Реакт предоставляет реактивность в виде перерендеринга компонента в ответ на передачу ему новых свойств от контейнера или его внутреннего состояния. Потребность есть в удобном и вычислительно дешевом способе обеспечить перерендеринг как реакцию на изменения внешних состояний, явно через свойства не передающиеся, чтобы не протасиквать какие-то глобальные состояния через всю иерархию.
Это если бэкпортить изменения. Если не убедите мейнтейнера, что ваши изменения нужны всем, то придётся до конца жизни с запозданием и продираясь через кучу конфликтов затягивать изменения из основной ветки.
Или просто все станут пользоваться вашим форком, а не оригинальным. Примеров тьма, когда если не всё, то значительная часть сообщества переходила на чей-то форк, потому что мейнтейнер не принимал реквесто нужных этой части сообщества.
писать код так, зная что всё API у тебя публичное гораздо сложнее, чем если хотя бы словах сообщил пользователям своего кода "это моё
Ну так на словах и сообщите. А уже пользователь пусть решает стоит ли ему рисковать.
Реакт предоставляет реактивность в виде перерендеринга компонента в ответ на передачу ему новых свойств
Нет, ручной вызов setState с последующей полной генерации дома и сравнения его с существующим — это ни разу не реактивность :-)
Потребность есть в удобном и вычислительно дешевом способе обеспечить перерендеринг как реакцию на изменения внешних состояний
А вот это уже частный случай реактивности.
Или просто все станут пользоваться вашим форком, а не оригинальным.
Я так развлекался с бенчмарками ToDoMVC. В одном было больше приложений, в другом были графики, в третьем был список приложений с галочками. В итоге мне пришлось всё это сливать вручную, так как форки основательно разъехались.
Архитектура приложения, особенно бэкенд части, обычно строго иерархическая. У нас есть подсистемы, в них модули, в них компоненты, в компонентах используются другие компоненты и т.д. Все хорошо и красиво, исключения редкие, это хорошо работает и хорошо ложится на ООП.
Во фронтэнде это тоже работает, особенно когда речь идет об отображении данных (или, в терминологии стейт-менеждеров, о "потоке данных из состояния в представление"). Это привело к тому, что многие UI фреймворки ориентированы на создание замкнутых сложных компонентов с внутренним состоянием (как правило, смесью данных, состояния приложения и состояния самого компонента + бизнес логика в смеси с логикой UI).
Это перестает работать хорошо, когда начинается взаимодействие с пользователем. UI паттерны для взаимодействия не иерархичны сплошь и рядом. Возьмем текстовый редактор. У нас есть меню (которое находится на верхнем уровне иерархии компонентов), которое изменяет состояние выбранного абзаца в документе (которые находится на самом нижнем уровне). Состояние пункта меню зависит от нижнего уровня. Это нарушает иерархию (в которой нижний уровень должен зависить от верхнего, но не наоборот).
Возникает проблема коммуникации от нижнего уровня к верхнему. Особенно это костыльно получается при использовании изолированных компонентов. Нарушается изоляция — компонент должен сообщать информацию о состоянии наружу (выбран ли текст, или выбрана картинка). Это, конечно, утрировано, для демонстрации проблемы.
App-state management framework (redux, flux, частично MobX) решают эту проблему радикально — они полностью развязывают иерархию компонентов и иерархию состояния приложения. Плюс они дают единую точку входа для любого действия (своего рода универсальный контроллер), и адресация к нужному элементу — это теперь обязанность контроллера.
Это заодно решает и другую, менее острую и реже встречающуюся, проблему — отображение одних и тех же данных в разных видах.
У этой архитектуры достаточно своих проблем, начиная от многословности и слабой связности, заканчивая тем, что привычные паттерны не работают, переиспользование — это боль и мучения (в отличие от "кинь на форму и настрой свойства" подхода с изолированными компонентами).
Главным вопросом при выборе архитектуры UI должен быть "насколько иерархия данных отличается от ирерархии компонентов". Если она не отличается — инкапсулированные классические компоненты будут отлично работать. Если отличается сильно лучше их сразу развязать, чтобы не городить костыли и в итоге не прийти к самодельному flux-у.
Есть подходы интересней, чем городить глобальную шину сообщений, где любой компонент подписывается на и кидает любые сообщения, и мы в итоге ничего не контролируем в этом клубке. Например, Высокоуровневый компонент, создавая вложенные компоненты может провязать их свойства. В результате они будут взаимодействовать друг с другом, ничего друг про друга не зная.
Для вашего примера с меню редактора:
$my_ide $mol_page
file_opened?val \
selection_current?val \
-
head /
<= Menu $my_menu
selection?val <=> selection_current?val -
body /
<= Panels $mol_book
pages /
<= Navigator $my_navigator
current?val <=> file_opened?val -
<= Editor $my_editor
file <= file_opened -
selection?val <=> selection_current?val -
Тут мы задаём меню, навигатор по файлам и собственно редактор текста, провязывая их свойства друг с другом, через свойства владельца. При этом, $my_menu, $my_navigator и $my_editor не знают друг о друге ровным счётом ничего. Но это не мешает им чётко друг с другом взаимодействовать: при выборе файла в навигаторе — он открывается в редакторе; при выборе текста в редакторе — активируются пункты меню, завязанные на работу с текстом; при выборе пункта меню — в редакторе соответствующе меняется текст.
В результате они будут взаимодействовать друг с другом, ничего друг про друга не зная.
Как это взаимодействие отлаживать?
Ну, вы вынесли информацию о выделенном элементе на самый верх, в корень глобального состояния, и расшарили его с меню. Это один из очевидных способов "починки" иерархии (вынести на общий уровень родителя), но с кучей недостатков: общие данные будут стремится на самый верхний уровень, нужны специальные механизмы для получения уведомлений об изменениях, потоки данных не однонаправленные.
Для redux можно решить задачу с меню вообще не имея общего состояния, а лишь подписываясь на события изменения выбранного элемента (и предусмотрев "null-событие" сброса выделения).
общие данные будут стремится на самый верхний уровень
Это не проблема, а достоинство, ибо есть только одно место где компоненты могут быть соединены — в месте их создания владельцем.
нужны специальные механизмы для получения уведомлений об изменениях
Не нужны (да, совсем), тут используется реактивное программирование.
потоки данных не однонаправленные
С этого мы и начинали. Они не могут быть однонаправленными. Вы, конечно, можете притворяться, будто обратного потока у вас нет, но ни к чему хорошему это не приведёт.
Обычно потоки данных всё же однонаправленные — какое-то входное событие (действие пользователя, событие XHR или WS, сработавший таймер) преобразуется в какое-то действие, изменяющее состояние чего-то внешнего по отношению к приложению — страница перед глазами пользователя, ушедший на сервер запрос и т. п. По дороге потоки могут разветвляться, сливаться, какие-то терминироваться, но крайне редко требуется зацикливание, передача данных назад.
В смысле "не могут быть однонаправленными" и "притворяться, что обратного потока нет"? Могут быть, и никто не отрицает обратный поток.
Однонаправленные потоки означает, что информация о данных для представления и информация о действиях пользователя на интерфейсе (из представления) доставляется разными путями (в противоположность, например, двустороннему байндингу).
И событие, к примеру, ввода попадает и обрабатывается в состоянии приложения, и лишь опосредованно изменяет само представление. В этом отличие от двустороннего связывания (как в вашем примере), которое одновременно меняет свойство и контрол. Это затрудняет реализацию некоторых сценариев (например, попробуйте нормально реализовать ввод по маске и тп).
Многие отрицают, смотрите выше :-)
Вы судите о двустороннем биндинге по его кривой реализации в Ангуляре. Нормальный биндинг тоже ничего сразу во вложенном свойстве не меняет, а сначала идёт всплытие нового состояния до точки принятия решения, а уже потом идёт в обратную сторону с актуальным значением.
Дальше можно не читать.
Умер ли MVC для фронтенда?