Comments 113
Сравнения с первым ангуляром вызывают сомнения.
Одной фразой «написали бизнес-логику»? А как же отдельные файлы под сервисы, под модули, связать это все потом?
Редьюсеры, экшены и мидлвари для компонентов? Ну так не пишите, это то же самое «написали бизнес-логику», только гранулярно и в разных файлах. Не нравится — пишите прямо в компоненте. Вся асинхронщина прекрасно кикается в componentWillUnmount.
И нет, не должно все оседать в middleware, middleware лишь процессит передаваемые в нее действия.
И нет, не нужен редьюсер на каждый чих, редьюсеров должно быть минимальное количество, стейт должен быть компактен и не раздут, и все необходимое выводится через селекторы.
Возможно пора двигаться дальше, посмотреть как все эти проблемы решали до нас более зрелые стеки?Так и решали: redux это пародия на CQRS+ES, прекрасно применяемые в огромных приложениях. А в качестве process-managers выступают саги.
Будущее
Компоненты контейнерыКонтейнеры — не компоненты с бизнес-логикой, а CQRS-обертка.
Selectors — ORM. И да, нам нужны не только getters но и setters;Нет, не так. Селекторы/геттеры — cQrs, экшены/сеттеры — Cqrs.
Нам нужен стандарт, но уже не “frontend”, а Универсального Приложения, работающего везде, от унитаза, до космического корабля?Но ведь и так уже все работает же?
Лол, а я как раз подумываю написать статью, что redux это счастье. Видимо мое удовольствие от работы === удовольствию Дена и === отсутствию магии.
Для меня крутость redux в детерменированности и декларативности.
Декларативность (в очень вольном толковании смысла этого слова) — в отличие от "дергания" каких-то функций и методов с какими-то побочными эффектами, redux дает простую кнопку "отправить action".
То есть на предыдущих фреймворках или вашем любимом jquery вы писали методы, за которые потом "дергали" и старались все это дело как-то упорядочить, то в redux эта структурированность из коробки. Всю побочку вы описываете в middleware или action creator'ах. Вся работа с данными приложения — ТОЛЬКО через reducer'ы. (А ваше желание "setter"ов демонстрирует полное непонимание самой идеи redux)
Я не беспокоюсь что что-то сломается когда я делаю dispatch(action), в отличие от, скажем, вызова метода сервиса ангуляра. Потому что самого сервиса больше нет и вся обработка бизнес логики структурированно находится в одном месте, а не размазана по куче этихъ самых сервисов.
И детерменированность — В каждый конкретный момент времени я знаю что именно повлияло на мои данные. Никаких "а вон в той не очень хорошо написанной директиве сработал $scope.$on() и что-то поменял в модели". No way. Более того, кроме этого осознания, я ещё и правда могу удобно посмотреть и отследить все действия, которые и привели к данному состоянию модели.
Это очень светлое ощущение чистоты кода, когда ты понимаешь, что само по себе ничего не произойдет, что бы там юзер где бы не кликал или что бы сервер не прислал.
А если это был просто истеричный инфоповод для идеи вашего Универсального Приложения — то, пожалуйста, посмотрите сюда: http://xkcd.ru/927/
К сожалению, не разделяю Вашего оптимизма по поводу redux, единственный и самый важный для меня фактор, это просто, а следовательно скорость разработки решения. С redux это увы не так.
Вопрос, если Вас не затруднит, используете ли Вы придуманные авторами redux способы или используете сторонние решения для:
создания action'ов?
создания actionCreater'ов?
создания бизнес логики?
Как Вы получаете данные из состояния?
Как Вы думаете, чем отличается «событийная система» redux, от скажем EventEmitter, чем она лучше?
Смотрели ли Вы на MobX?
Вы говорите "скорость разработки", а указываете на скорость setup'а.
Почему в вашей табличке, например, указаны middlewares? Вы что, часто пишете middleware для компонентов? И почему "зарегистрировать reducer" это не "подключить компонент"? Почему у вас в angular 1 не указано какие файлы надо создать? Вы прям в одном файле пишете и шаблон и контроллер и модель и бизнес-логику?
Что значит "связать actions, reducers с компонентом?" Сделать импорт action creator'ов?
А в первом ангуляре вам не надо файлы импортировать и делать app.directive('abc', ['123']) ?
Это первое. И второе:
Почему вы разделяете написание action'ов, reducer'ов с бизнес-логикой? Где вы пишете бизнес-логику если не в redux?
Action'ы и reducer'ы это и есть бизнес-логика.
Нет, понятно что во всем хороша мера, и что всё равно нужен какой-нибудь объект модели, со своими методами и прочим, но он должен заниматься именно моделью и её данными.
Как Вы думаете, чем отличается «событийная система» redux, от скажем EventEmitter,
В redux односторонний, централизованный и единый поток. Если я отправляю action, я 100% уверен что оно ЛИБО отправило другие действия, ЛИБО изменило что-то в store.
Всё. Больше нет вариантов.
Я могу включить дебажный middleware и посмотреть какие именно действия отправило данное действие.
Если я отправляю событие в EventEmitter, где мне потом собирать обработчики данного события? Как мне посмотреть кто и как обработал событие?
Сделать единый EventEmitter с интерфейсом для middleware и четким разделением "это событие меняет данные" / "это событие отправляет другие события"? Ну дык это и есть redux.
На mobx не смотрел, но судя по страничке они сделали что-то типа того же redux, только с декораторами и обернули селекторы в observable.
единственный и самый важный для меня фактор, это просто, а следовательно скорость разработки решения
Так вот redux со всей свой экосистемой селекторов, редусеров и действий оказывается проще огромной мешанины из сервисов, фабрик, вычисляемиых и зависимых значений и прочего.
Если вы не поддерживаете проекты больше месяца и хотите ляпать по SPA в неделю, то тогда да, redux не для вас. Только тогда не надо истерить на всех, ок?
А вот и неправда. Отправка action'а приводит к любым последствиям, в зависимости от того, что у вас стоит в middleware (и, с-но, к любым абсолютно сайд-эффектам). Оно вам хоть ракету запустит, асинхронно, фоном. Предсказуемости, конечно же, нету никакой — т.к. раз экшоны в middleware обрабатываются асинхронно (и асинхронно спавнят другие экшоны), то порядок их спавна и обработки недетерменирован. То есть, в зависимости от удачи, исполнение одного и того же экшона может приводить к разным результатам.
> Если вы не поддерживаете проекты больше месяца и хотите ляпать по SPA в неделю, то тогда да, redux не для вас.
Вы явно никогда не видели проекта, в котором количество экшонов на одной странице измерялось бы сотнями. В этом случае обычно извращаются кто как — вплоть до прикладывания к коду схем КА, потому что из самого кода, в силу redux'овской бессвязаности, понять ничего нельзя в принципе.
Ну middleware-то посмотреть не проблема. Понятно что без знания проекта уверенности быть не может.
Суть такая что все сайд эффекты локализованы в action-creator'ах и middleware. Это дает значительно больше уверенности, нежели когда сайд-эффекты размазаны по структуре сервисов (в том же angular). И ракету оно запустить может, да, но вот данные поменять в store — нет. увы. Может выслать другой экшн, но напрямую поменять — нет.
Предсказуемости, конечно же, нету никакой — т.к. раз экшоны в middleware обрабатываются асинхронно
А promise или observable возвращать не?
Можете привести пример экшона который отправляет кучу асинхронных экшонов и не может вернуть промис когда всё основное закончилось?
Да его смотреть не надо, понятно, что там гарантированно будет что-то для обработка асинхронщины, и этого уже достаточно, чтобы вся «красота и функциональность» редакса чудесным образом исчезла.
> Суть такая что все сайд эффекты локализованы в action-creator'ах и middleware. Это дает значительно больше уверенности, нежели когда сайд-эффекты размазаны по структуре сервисов
Чем размазывание по сервисам отличается от размазывания по асинхронным экшонам? Там разница в коде — минимальная и чисто синтаксическая.
> А promise или observable возвращать не?
Возвращать, извините, откуда? store.dispatch(action) — вот здесь, из dispatch?
Возвращать, извините, откуда? store.dispatch(action) — вот здесь, из dispatch?
Эээ, очень странный вопрос. Откуда ещё-то? Только не говорите, пожалуйста, что вы не знали, что dispatch пробрасывает возвращаемое значение от милварей
Это никак не отменяет того, что порядок, в котором простые экшоны потом придут к вам в редьюсер, не определен.
экшонов кроме одного не существуетНу как это. Вся фишка в возможности обрабатывать в одном редьюсере разные экшены.
грязный вызов грязных эффектов.Вообще-то нет. doAnything() не содержит в себе эффекта, эффект описан в саге как реакция на результат doAnything, который является декларативным описанием, что вообще произошло в приложении. А если у вас внутри doAnything промисы, thunk'и и прочая белиберда, так это значит, что идея redux нарушается.
Разные _типы_ экшонов. А сам экшон вы обрабатываете каждый раз один единственный — который к вам прилетел. И про другие вы вообще не в курсе.
> Вообще-то нет. doAnything() не содержит в себе эффекта, эффект описан в саге как реакция на результат doAnything
Давайте начнем с того, что вообще, по дефолту, в редаксе никаких саг нет. И если, например, те же миддлеваре для промисов упоминаются в доках, то саги — не упоминаются.
Надо понимать, что, фактически, саги имеют очень мало общего с редаксом в плане архитектуры — они просто используют миддлеваре-инфраструктуру редакса как базу для интерпретатора своего дсл'я. Интерпретатор (сага-мидлеваре) по одному принимает сага-команды и исполняет их. Заметьте — вы даже можете полностью отказаться от редьюсеров и писать вместо них так же саги (некоторые так и делают). Тогда от редакса ничего кроме лупа интерпретатора внутри dispatch вообще не остается!
> А если у вас внутри doAnything промисы, thunk'и и прочая белиберда, так это значит, что идея redux нарушается.
Ну как это нарушается? С промис-мидлеваре у вас там и будет промис, вот прям самый что ни на есть тупо-промис, обыкновеннейший, чистейший. И это более «дефолтное» для редакса решение, чем саги (которые вообще не стоит рассматривать в контексте архитектуры редакса в силу указанных выше причин).
Разные _типы_ экшонов. А сам экшон вы обрабатываете каждый раз один единственный — который к вам прилетел. И про другие вы вообще не в курсе.Блин, ну понятное дело. (state, action) => state.
по дефолту, в редаксе никаких саг нетПо дефолту и thunk'ов нет. Все синхронно.
те же миддлеваре для промисов упоминаются в доках, то саги — не упоминаютсяДоки
саги имеют очень мало общего с редаксом в плане архитектурыПравильно, потому что редакс идеологически синхронен. И вся асинхронщина из него выносится в сторону вместо попыток упаковать все в несчастные экшены.
тупо-промис, обыкновеннейший, чистейшийПромис не чист по определению, мы это уже обсуждали. Так как является эффектом, а не его декларативным описанием.
дефолтноеВот об этом вообще нигде не слова, с чего вы взяли.
Это просто ссылка на все связанные проекты в конце. А про промис-миддлеваре написано прямо в разделе миддлеваре.
> Правильно, потому что редакс идеологически синхронен.
Потому что он не о том. Редакс — про изменение стейта через композабельные редьюсеры, а саги — про декларативное описание эффектов. То, что саги прикручены к редаксу — это вообще исторический казус (там от редакса реально используется десяток строк кода). И у них, насколько я помню, даже issue был на откручивание.
> Промис не чист по определению, мы это уже обсуждали.
«чистейший» в смысле «чисто промис», а не в смысле чистой функции в терминологии фп :)
У вас какой-то особый многопоточный js? Ещё раз сначала: Я сделал dispatch — оно прошло до конца и либо отправило экшоны, либо поменяло данные. Никакой асинхронный экшон за это время не прилетит. ДА, оно может заспавнить асинхронные сайд-эффектовые экшоны. Которые никак не повляют на данные, потому что когда им придет время резолвится, они точно так же отправят простые экшоны в стор. И, если вам важен порядок прихода этих экшонов в стор (что уже подозрительно) — то у вас есть все инструменты чтобы это обработать.
Я прекращаю дискуссию, потому что вы не приводите примеров, а просто пугаете какими-то абстрактными сотнями асинхронных экшонов на страницу. Такое ощущение, что реально пишите что-то типа
const asyncAction0 = () => (dispatch, getState) => {
dispatch(asyncLoadRocketWithBomb()) // Плохо
dispatch(asyncLaunchRocket()) // ААА
dispatch(killAllHumans()) // Асинхронность
dispatch(asyncProfit()) // Непонятно резолвятся
}
Тут я уже помочь не могу, я пытался.
Вы сами себе противоречите. Если асинхронный экшон может отправить в стор обычный — то этот асинхронный экшон МОЖЕТ влиять на данные. Тот факт, что вы делаете это через промежуточный вызов — никак не сказывается вообще на control-flow вашего приложения. Нет никакой разницы между вызовом экшона и вызовом функции, апдейтящей стор напрямую.
Давайте я попробую привести пример.
Допустим, в какой-то момент делается запрос к серверу. Где — не важно, это может быть любое место в коде (редюсер, мидлварь, компонент) на ваш выбор:
fetch("http://example.com/api/get/some/data")
.then(r => r.json())
.then(data => dispatch({ type: DATA_RECEIVED, data: data }))
Какая с таким кодом может быть проблема? А очень простая.
- Пользователь делает действие, которое приводит к выполнению этого запроса.
- Запрос успевает обработаться сервером, но подвисает из-за сетевого лага.
- Пользователь (возможно, другой) делает действие, которое меняет разделяемое состояние на сервере.
- Пользователь (первый) повторяет свое действие, запрос на получение данных приходит второй раз.
- Приходит ответ на запрос, отправленный на шаге 4.
- Приходит ответ на запрос, отправленный на шаге 1.
Если не предпринимать никаких специальных усилий — то в итоге пользователь увидит устаревшие данные, думая что они — свежие.
Ага, только вот эти самые специальные усилия мне будет принимать намного легче, потому что я могу отловить эти самые DATA_RECEIVED, а не они тихо проапдейтятся где-то через сеттер или обсервабл.
Понимаете? На самом верхнем уровне будет цепочка экшенов:
DATA_RECEIVED (от шага 4)
DATA_RECEIVED (от шага 1)
Поэтому я и писал, что "уверен 100%" — я отправляю fetchData(), и никакие данные она мне не поменяет потом втихую. Она пошлет экшон на изменение данных.
Redux не дает магическим образом дополнительный функционал, скорее наоборот, в противоположность магии он делает только то, что написано и в этом-то и есть то самое "счастье разрабатывать под redux". Потому как всё под контролем — хочешь дополнительный функционал? Вот возможности. Не хочешь — как хочешь.
А если для человека "Нет никакой разницы между вызовом экшона и вызовом функции, апдейтящей стор напрямую.", то как бы окей, пускай обратно идет в свои джиквери, там вообще минимум усилий — прямо во вьюхе вызвал fetch, там же и обновил div, ничо рендерить не надо, удобно, чо.
А есть какие-то проблемы в том, чтобы залогировать точно так же обычные функции?
> А если для человека «Нет никакой разницы между вызовом экшона и вызовом функции, апдейтящей стор напрямую.»
Это не для меня ее нет, ее по факту нет. Вы никак не сможете по поведению определить, был ли вызван экшон, или стор обновили «напрямую».
"отловить" = "дописать функционал, например сверяющий время отправки", а не залогиировать, хватит троллить.
Например я могу добавить в интерфейс DATA_RECEIVED параметр "время отправки", а в редусере сравнивать время. Или сделать как в доке и не позволять отправить второй запрос пока идет первый.
Это не для меня ее нет, ее по факту нет. Вы никак не сможете по поведению определить, был ли вызван экшон, или стор обновили «напрямую».
Теперь вы противоречите сами себе. То у вас ужасные, мерзкие асинхронные мидлвари разворачивают поведение экшона на 180°, запускают ракету, то вдруг экшон который проходит через те же самые жуткие мидлвари ничем не отличается от сеттера (которых, кстати, внезапно может быть много, потому что сеттер это дело сервиса, а экшон — дело глобальное)
Например я могу добавить в интерфейс DATA_RECEIVED параметр "время отправки", а в редусере сравнивать время. Или сделать как в доке и не позволять отправить второй запрос пока идет первый.
Ну а я на mobx могу добавить в текущий скоуп параметр "время отправки", а в обработчике его сравнивать. Или не позволять отправить второй запрос. Или отменить первый запрос.
И на knockout я могу сделать так же.
И даже на jquery я могу сделать то же самое. Абсолютно то же самое.
Redux никак не помогает это делать.
И чем же, по-вашему, тихая отправка DATA_RECEIVED отличается от тихого апдейта через сеттер?
Возможно в комментарии выше вы проглядели… СЕТТЕРОВ МОЖЕТ БЫТЬ НЕСКОЛЬКО, так видно?
DATA_RECEIVED отправляется экшн креатором, который имеет определенный интерфейс.
Так вот redux помогает это делать путем явного объявления, так что вам не придется искать где вы там на jquery сныкали setterы. которых, опять же, может быть неско… оу, я уже писал это. А ещё экшен проходит через middleware.
Прямо такой унифицированный интерфейс для доступа к единственному источнику правды.
И даже на jquery я могу сделать то же самое. Абсолютно то же самое.
Как вы из "помогает" вывели "без redux этого никак не сделать"?
Просто пожелаю удачи когда у вас появятся 2 компонента которым нужны эти данные и вы со своим jquery будете писать jquery-сервис, лол
ВЫЗОВОВ dispatch ТОЖЕ МОЖЕТ БЫТЬ НЕСКОЛЬКО
Если вы так не делаете — это потому что вы не разбрасываете грабли в своем же коде для себя самого.
Но точно так же можно и не создавать второй сеттер.
так что вам не придется искать где вы там на jquery сныкали setterы
А зачем мне их ныкать куда-то?
Кстати, если доводить до абсурда — то и в redux редюсер или мидлварь можно так заныкать, что вы его не найдете.
Просто пожелаю удачи когда у вас появятся 2 компонента которым нужны эти данные и вы со своим jquery будете писать jquery-сервис, лол
На самом деле, достаточно двух функций (ну, еще защиту от гонок добавить — но она делается одинаково):
function RequestData() {
fetch("http://example.com/api/get/some/data")
.then(r => r.json())
.then(DataReceived)
}
function DataReceived(data) {
// ...
}
ВЫЗОВОВ dispatch ТОЖЕ МОЖЕТ БЫТЬ НЕСКОЛЬКО
dispatch (несколько) => action ОДИН => middleware => store
setters (несколько) => store
А зачем мне их ныкать куда-то?
Кстати, если доводить до абсурда — то и в redux редюсер или мидлварь можно так заныкать, что вы его не найдете.
сеттеры вы пишете в отдельной папке setters? или все таки в компонентах/сервисах? вот туда и заныкаете вместе с fetchData. В redux всё по полочкам.
Нет. окей, вы взяли свою (и всех программистов на проекте) волю в кулак и создаете эти сеттеры, кладете их отдельно чтобы вызывать из сервиса, лол, создаете некий объект в который будет класть данные DataReceived, как-то подписываетесь на его изменения.
Теперь добавьте middleware и у вас будет… убогий hand-made redux. Ура!
Вы упускаете из виду тот факт, что обсуждаемый dispatch вызывается по окончанию обработки запроса — а значит, несколько вызовов dispatch означают, что один запрос отправляется из нескольких мест.
Настоящая причинная цепочка выглядит так:
X => fetch => dispatch (несколько) => action => middleware => store
X => fetch => setters (несколько) => store
Ситуация, когда у вас есть два разных X, будет мешать вам разбираться с гонкой независимо от того, используете ли вы dispatch или setters — потому что для борьбы с гонкой вам надо менять не только получение данных, но и отправку запроса, какой бы способ вы не избрали (кроме, пожалуй, серверного времени в ответе — но, допустим, его там не передается, а api менять нельзя).
И в обратную сторону это тоже работает — до тех пор, пока я следую DRY и не делаю один и тот же запрос к серверу из разных мест в коде — у меня нет проблемы с разными сеттерами.
Это вы о чем? Как раз на сеттер я прыгну по go to definnition, а вот сныканные экшоны мне придется искать.
Замечательно. Кто мешает все то же самое сделать с обычными функциональными вызовами? Это же достигается одной единственной функцией-оберткой.
> То у вас ужасные, мерзкие асинхронные мидлвари разворачивают поведение экшона на 180°, запускают ракету, то вдруг экшон который проходит через те же самые жуткие мидлвари ничем не отличается от сеттера
Все верно, мидлвари ничем не отличаются от сеттера — такие же грязные, такие же непредсказуемые в плане сайдэффектов. Я нигде себе не противоречил.
И чем же, по-вашему, тихая отправка DATA_RECEIVED отличается от тихого апдейта через сеттер?
К тому же он нарушает one-way-data-flow, который сам же и пропагандирует, так как теперь состояние «асинхронная операция завершена» приходит не через стор.
Еще он нарушает инкапсуляцию экшенов, раскрывая, что внутри диспатча оказывается промис. И то! Только если не забыть вернуть. Так себе решение.
Thunk и Promise мидлвари были введены как простое и быстрое in-place решение но в ущерб идеологии.
Но ведь кнопка «отправить action» — это и есть функция с побочными эффектами.
Ваша табличка ну вообще ничего общего не имеет с реальностью.. Количество действи для достижения успеха явно разнится.
Это мы ещё опускаем такой факт, что по хорошему, нужно создавать и отдельные сущности как для работы с бизнес логикой, так и для работы с измением данных.
А говоря про функционал, тут уже зависит от человека, от его опыта, имхо.
Прошу прощения, но с таким же функционалом на каком-нибудь jquery я могу написать прямо в одном файле в index.html в script-тэге, это совсем не показатель.
Хотя полностью согласен с автором что Frontend в последние годы усложнился в несколько раз, но прирост производительности увы не соответсвует такому же тренду.
Вероятно, вы не умеете готовить Redux. Пример чтения данных из API в форму и записи из формы в API с валидацией (на выбор варианты redux-saga/redux-thunk), применяя ducks-pattern.
Это и является причиной появления надстроек над самим redux, что опять же подчёркивает проблематику.
Пара вопросов, для ответа по желанию:
1. Если инструмент не эффективен, зачем он нужен? Ради себя самого?
2. Как вы пришли к использованию redux? Какие проблемы Вашего приложения он решал?
Давайте начнем с начала. Для чего нужен Redux? Для управления состоянием приложения. И надо отметить, что эта задача выполняется очень хорошо. Redux задает четкие правила изменения данных в приложении, вы всегда знаете что именно вызвало изменение состояния. Однонаправленный поток данных, изменение состояние через простые чистые функции, которые легко тестировать. И все это достигается просто из коробки, без дополнительных инструментов. Да, конечно, цена этому некоторый шаблонный код (имена экшенов, экшен-крейторы и т.д.), но это ни «сложно», ни «неэффективно». Скажите, вы действительно на каждый новый компонент создаете новый редюсер, экшен? Не знаю как у вас, но по-моему опыту большая часть компонентов — это глупые компоненты, они просто не требуют тех шагов, что вы описали в своей табличке. Мне кажется, что вы преувеличиваете проблему.
Работа с redux сложна неэффективна. Это и является причиной появления надстроек над самим redux, что опять же подчёркивает проблематику.
А не думаете, что причиной появления надстроек является расширяемость Redux? Redux отлично решает проблемы с управлением синхронными данными, но для сайд-эффектов он не столь удобен. Зато есть API для создание middleware — расширения функционала. Эта возможность породила такие замечательные инструменты как redux-thunk, redux-observable, redux-saga (мой выбор). Причем это порождает конкуренцию, одни инструменты лучше других, и вы утверждаете, что это негативная сторона?
Имхо, redux лучшее решение для менеджмента состояния. Ни reflux, ни mobx, ни angular не давали для меня такой уверенности в работе приложении как это делает redux.
Если инструмент не эффективен, зачем он нужен? Ради себя самого?
ИМХО, вопрос не имеет смысла ибо содержит в себе ложную информацию.
Мне кажется, что вы делаете ошибочные выводы.
Ради этого мой пост и существует, поделиться мнением, возможно дать пищу для ума, возможно узнать для себя что-то новое — изменить мнение.
Управление состояниями, а полноценная база данных не решила бы этот вопрос?
Диспетч
А не думаете, что причиной появления надстроек является расширяемость Redux?
Я бы согласился с Вами, если бы эти надcтройки занимались расширением функционала, но по факту, это просто автоматизации рутинных процессов или внесения конструктива в синтаксис, предлагаемый из коробки.
Но чего у redux не отнять — это middleware, вот они просто супер, хоть и нарушают в текущем виде SOLID.
возможно узнать для себя что-то новоеИ это прекрасно!
Управление состояниями, а полноценная база данных не решила бы этот вопрос?Не решила бы, так подавляющее число баз императивны. Редакс же позволяет представлять хранилище в виде композиции. Другое дело, что механизм middleware предоставляет вам возможность все это дело персистить, легко, просто и удобно. И это тоже прекрасно!
Диспетч событий экшенов, почему бы не использовать шаблон наблюдатель?Потому что observer подразумевает императивную работу, тогда как редакс, опять таки, не про это.
Я бы согласился с Вами, если бы эти надcтройки занимались расширением функционала, но по факту, это просто автоматизации рутинных процессов или внесения конструктива в синтаксис, предлагаемый из коробки.UI в сущности своей синхронен, ведь в 100% случаев является функцией от состояния. Это состояние может быть размазано по разным слоям с прямым доступом (как, например, в классических императивных подходах), а может быть собрано воедино в одном месте декларативно (да, через ту самую композицию), что и предоставляет редакс. Почему же тогда добавление «надстроек» над этим механизмом для, например, обработки асинхронщины, длинных транзакций и т.п. не является расширением?
SOLIDНе уверен что SOLID в полной мере применим к функциональной парадигме.
Вы здесь не правы. Сравнивая с теми же сагами: саги = observable с формальной точки зрения, это просто разный интерфейс для обращения к одной и той же сущности (генераторы и observable полностью взаимозаменяемы). То есть вы пишете одно и то же (там и код-то получается предельно похожим), и с точно тем же результатом, просто несколько «на другом языке». Собственно, есть же: https://redux-observable.js.org/ с тем самым observable-интерфейсом для саг (которые называются эпиками), аналогично, в ngrx/store для обработки состояния тоже свои observable-саги, именуемые эффектами.
В чем саги выигрывают: генераторы — это поддерживаемая на уровне языка идиома, а observable — эмуляция. С-но, решение на сагах, назовем так, «чище» в плане отсутствия лишних сущностей.
То есть важно, как mobx с этим observable работает. Именно в этом зарыта императивность, а не в самом наличии observable.
Ну и выше был не только контекст mobx'a, речь шла и об observable-коллекции экшонов с диспатчем по подписке — а это практически в чистом виде effects из ngrx (да и сам стейт в ngrx тоже имеет observable-интерфейс).
И как же mobx с этим observable работает, что аж императивность тут зарыта?
> То есть важно, как mobx с этим observable работает. Именно в этом может быть зарыта императивность, а не в самом наличии observable.
А по самому вопросу — например, она может выражаться в том, как изменяется в mobx стейт (через аналог двустороннего биндинга вместо изоляции при помощи actions)
Поясните почему action в mobx являются недостаточно изолированными...
есть, как минимум, два разных варианта работы с редаксом через observable
ещё mobx-state-tree умеет прикидываться redux-ом:
import React from 'react'
import { render } from 'react-dom'
import App from './containers/App'
import 'todomvc-app-css/index.css'
import { Provider } from 'react-redux'
import todosFactory from './models/todos'
import { asReduxStore, connectReduxDevtools } from 'mobx-state-tree'
const initialState = {
todos: [{
text: 'learn Redux',
completed: false,
id: 0
}]
}
const todos = window.todos = todosFactory.create(initialState)
const store = asReduxStore(todos)
connectReduxDevtools(require("remotedev"), todos)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Покажите как с помощью redux сделать так: app.IsLoading = app.postForms.some(f => f.IsLoading)
Поле вычисляемое, может лучше сделать его через селектор и не хранить в сторе? (Стор должен быть минимальным и хранить минимум инфы, всю выводимую/вычисляемою инфу нужно выносить в селекторы).
А селектор так и будет выглядеть:
const isAppLoading = createSelector(
[postForms],
any(prop('isLoading'))
);
const reset = createAction(`${NS}RESET`)
const set = createAction(`${NS}SET`)
const setField = createAction(`${NS}SET_FIELD`)
const setErrors = createAction(`${NS}SET_ERRORS`)
const setError = createAction(`${NS}SET_ERROR`)
const setSubmitting = createAction(`${NS}SET_SUBMITTING`)
const save = () => ({ type: `${NS}SAVE` })
Это уже чит, так как вы не используете явных констант, более того — ваши константы вычисляются, а значит, вы сходу потеряли возможность типизации пейлоада внутри редьюсера. А поскольку createAction не имеет никакой информации, кроме константы типа, то и типизация action creator тоже потеряна.
Далее, представьте, что у вас там не одно поле — а 30, а валидацию надо проводить по ходу заполнения полей (а не скопом при отправке). Кажется, предложенное решение не слишком хорошо на это дело скейлится, в любом случае.
И последнее:
const reducer = createReducer(
{
[reset]: () => ({ ...initialState }),
[set]: (state, post) => ({ ...state, ...post }),
[setField]: (state, { key, value }) => ({ ...state, [key]: value }),
[setErrors]: (state, errors) => ({ ...state, errors: { ...errors } }),
[setError]: (state, { key, error }) => ({
...state,
errors: { ...state.errors, [key]: error },
}),
[setSubmitting]: (state, isSubmitting) => ({ ...state, isSubmitting }),
},
initialState,
)
Вы же тут сделали первый важный шаг — заменили стандартный редьюсер с кейзом по типам на набор функций (каждая функция соответствует одному экшону), а имя функции — привязывается к самому экшону. Почему бы не сделать второй шаг — зачем вообще нужен экшон и пейлоад, если можно просто передать содержимое пейлоада в аргументах функции? Т.о. пейлоад — аргументы ф-и, имя экшона — имя ф-и, сама ф-я автоматически в себе содержит action creator, а вызов следует приравнять к диспатчу action'а. В результате у вас одна обычная функция вместо целой машинерии.
Почему бы не сделать второй шаг
Мопед не мой. Покажите пример кода, как вы себе представляете API. Я может быть возьмусь за реализацию.
вы сходу потеряли возможность типизации пейлоада внутри редьюсера
не вижу преград для типизации { key, value }
[setField]: (state, { key, value }) => ({ ...state, [key]: value }),
вы не используете явных констант
А они не нужны, используются только для дисплейных имён в Redux DevTools.
Дисклеймер: я ни разу не профессиональный фронтенд-разработчик, поэтому мое мнение — безграмотное имхо.
И опыт react крайне мал, несколько простейших для-себя сайтов
Как-то я пытался изучить Redux, но в итоге мне это не понравилось. Конечно, идея однонаправленного потока данных хороша, но очень, очень много boilerplate кода. Слишком.
Да и Redux не полностью использует сам React (поправьте, если ошибаюсь, но вроде там не используется state самих компонентов?)
Не так давно пытался научить мидля писать на redux, да я не лучший учитель конечно. Но после того, как человек сдал задание на этом стеке, успешно сдал задание, вывод был тодлько один — больше не писать на redux и ко.
Говарят, что сложность redux — «защита от дураков». На мой взгляд — сложность redux — ломание системного мышления специалистов. Хорошо это или плохо — не знаю.
Вы сравниваете «горячее» и «круглое», потому как angular это уже огромная инфраструктура, в то время как react — это только представление, а redux — это менеджер состояний.
Считаю что redux в этом смысле больше следует философии UNIX, чем концепция «давайте сделаем все в одном».
Описание экшенов и прочего — это скорей элемент бизнес-логики чем интерфейсный. Тем более это дает вполне приятно тестировать эту самую логику.
Но если вам не нравиться redux — есть например altjs (mutable state) или другие имплементации flux.
Один из главных посылов, что я тут пытался высказать:
Ценность инструмента, прямо пропорционально количеству работы, что он упрощает
И вот redux чистом виде, на мой взгляд, не удовлетворяет этому. Redux слишком низкоуровнев.
Да, сообщество уже создало множесто решений для упрощения работы, и коллеги выше привели часть из них, но не делает ли это проблему ещё более насущей?
А если интсрумент плох, имеен ли смысл его использовать только потому что он захайпился?
С таким же успехом можно сравнить и vuejs. (сравнивать с angular1 — это не справедливо)
К сожалению, с vuejs знаком только в формате демок, в реальном приложении ещё не довелось использовать, но то что я для себя вынес — vuejs на одном уровне с react, тогда когда angular1 ~= (react + redux) — (routers + ...). И я не в коем случаи на сравниваю react с angular1, извиняюсь, если ввёл Вас в заблуждение.
Советую познакомиться поближе, после реакта почему-то на vue кайф писать.
Есть конечно там своя бюрократия, но ее не очень много.
Да и приятностей встроенных в 28 гзипнутых килобайт довольно много, я удивился когда в итоге под каждый случай находил безкостыльное решение в самом фреймворке.
И мне очень понравилась документация, всем бы такую.
Ну и vue-cli мне понравился сильно больше чем create-react-app, никаких танцев с бубном чтобы подключать scss, или, например, pug.
C тайпскриптом пошаманить придется, но сегодня увидел что умельцы уже сделали все готовое https://habrahabr.ru/post/330400/ .
Если нравится jsx — то на нем тоже можно писать во vue.
Тогда на помощь приходит jQ, которая делает реально огромную работу за программиста, а главное делает её легкой.
Потом, уже закореннелые новички читают о фраймворках и выбирают реакт! Молодцы! Искренне. Они его открывают и точно так же как и в первые моменты своего начала, они сидят с отрешенным выражением лица не зная что делать. И тут приходит человек, говорящий и показывающи-тыкающий носом, как нужно написать приложение с помощью Redux. Все обожествляют творца вначале, а тех кто его критикует — минусуют и банят!
И вот пролетает ещё полтора года и человек пишет подобное.
Но это не из-за того что создатель Redux плохой, или из-за того что реакт кривой или ещё что-то…
Дело в том что ДАЛЬШЕ никто носом не тыкает! Ведь если разобраться, редакс, это только малая часть архитектуры, которая нужна в реальности. Просто те кто это знает не говорит об это из-за тог что их когда-то за подобное забанили или по каким-то другим причинам. а в большинстве случае и не знают, так как реакт вообще не очень полюбили те, кто реально знаком с архитектурой.
Поэтому остается только ждать, пока подрастет коммунити реакта и начнет учит-вести за ручку, погружая в архитектуру, в которой редак является лишь одним кирпичиком огромного minecraft.
К счастью был создан React. С ним гораздо медленнее старт проекта, но зато дальнейшая разработка гибче и проще чем с Angular 1.
Когда я попробовал популярный к тому времени Redux, я опечалился и мне захотелось попробовать Angular 2). Потом подумал, ну не так хорош Redux, чтобы на долгие годы стать стандартом, через 2-3 годика что-нибудь другое наберет популярность. В общем, я отказался от использования Redux в новом проекте. В голове было свое решение, основанное на библиотеке object-path, которое недолго было реализовывать, что я и сделал. Писать велосипед обычно плохое решение, но что делать, если достаточно хорошей библиотеки нет.
Потом набрал популярность Mobx. Поразбиравшись с ним немного, решил, что это приемлемое решение и в следующем проекте использовал его. Оказалось, что в реальном приложении кода приходится писать немного больше, чем в примерах. Но это мелочи. При написании я стал чувствовать себя сапером) Постоянно приходилось прокручивать в голове – все ли я учел, ничего ли не забыл, а то что-нибудь снова
Решил вернуться к своему решению, доработав его немного. И тут мне пришла в голову идея, как его прям сильно улучшить, что я и сделал.
В общем, в текущем проекте я пишу кода даже меньше, чем при использовании mobx. Причем без всякой магии и при этом реактивность осталась – компонент обновляется при изменении какого-нибудь свойства в объектах стора или стейта.
К чему я все это?
Просто хочу сказать, что если прям чувствуешь, что это недостаточно хорошее решение, так зачем его брать? При выборе надо думать своей головой, а не руководствоваться только количеством звёздочек на гитхабе, как делает большинство. Понятно, что это не всегда возможно ввиду отсутствия времени или других факторов.
1. Апатия: Выбор каждого, как и чем пользоваться, не нравится redux, выбирай что нравится.
2. Смерение: Не будешь сейчас использовать redux, можно даже не расчитывать на достойные предложения о работе, случись то, то что нужнен реальный опыт использования.
3. Страх: Сбербанк задумался над использованием в своих решения. Сколько уже стартануло проектов? Уверены ли мы, как индустрия, что это именно то, что нужно?
Так получилось, что мы самое свободное и децентрализированное сообщество программистов. Наверно это не удивительно, учитывая всю историю web. А с другой стороны наш «frontend» ещё младенец, по сравнению с другими стеками технологий. Значит ли это, что просто нужно подождать и переболеть всеми полурешениями? Лично мне кажется, что стоит заглянуть в родственные стеки используемых в Android, iOS приложених перед которыми стоят похожие проблемы и проанализировать как они решили их.
К чему эти дифирамбы? Просто на главной странице документации redux написано, что его можно использовать с чем угодно, не только с react-ом, но и с Angular 2+. Но за все время разработки на последнем, я так и не смог придумать, для чего мне мог бы пригодиться redux.
- деление компонентов по типу Presentational и Container Components (https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0)
- каждый «корневой» компонент (как правило, привязанный к опредленному роуту) получает свое актуальное состояние с бэка при инициализации или изменении параметров роута
- корневые компоненты между собой не общаются, только через бэк
- корневой компонент как правило является Container Component-ом и для отрисовки передает/получает свойства своего состояния через цепочки input/output дочерним Presentational Component-ам (или, если куски большие, разбивать на несколько дочерних Container Component-ов
Как правило, если все сделать грамотно, деревья не получаются слишом многоуровневыми и никаких проблем при передачи состояния вглубь и получения изменений через output-ы лично у меня не возникает.
Эти принципы подробно описаны в официальной документации Angular-а и, как мне показалось, являются тем самым angular way.
В редких случаях, допускаю хранение некоторых глобальных флагов и пр. в storage-ах.
Если стейт хранится на беке, то как его потом синхронно обновить? Вот у вас есть несколько несвязанных компонент, каждая из которых подтягивает копию некоторого стейта с бекенда, в одной из компонент стейт обновили => он ушел на бекенд, как обновить остальные копии стейта, в других компонентах? Какая часть программы отвечает за то, что остальные компоненты сделают в этот момент запрос к бекенду и обновят свой стейт? Или в таких случаях только руками через output и надеяться, что не придется прокидывать слишком далеко?
> Эти принципы подробно описаны в официальной документации Angular-а и, как мне показалось, являются тем самым angular way.
Я точно не помню сейчас уже, но мне казалось, что в доках описывается вариант обмена данными между компонентами через сервис.
Вот у вас есть несколько несвязанных компонент, каждая из которых подтягивает копию некоторого стейта с бекенда
Исходя из моих принципов, у меня в любой момент времени нет нескольких несвязанных компонентов, общающихся с бэком. Всегда есть только один корневой (грубо говоря, текущая страница) привязанный к текущему роуту. Если роут меняется (не путать с изменением параметра роута), то этот компонент уничтожается, и создается новый компонент согласно новому роуту. И уже он грузит свой актуальный стейт с бэка.
Т.е., поставленную вами задачу я бы решал следующим образом: создал отдельный компонент Layout, который будет показываться по-умолчанию на страницах и содержит компонент c шапкой, в котором отображаются данные авторизованного пользователя, которые берутся из сессии (т. е. из storage-а). Обновлять данные в шапке можно по ngDoCheck, а чтобы это никак не сказалось на производительности, в механизм нашей сессионной модели несложно добавить проверку на последнее изменение по Timestamp-у.
Конкретно всё это безобразие можно реализовать и другими способами (от предложенного общего сервиса, до синглтона-хранилища-состояний). Но у использования того же localStorage в данном конкретном примере очевидны свои плюсы.
Но это, опять же, если я правильно понял ваш пример. Часто годятся и «оутпуты до корня». Пример: личный кабинет пользователя — корневой компонент. При создании загружает актуальные данные о пользователе и хранит их в себе. Имеет два дочерних компонента: Presentational — лэйаут (использую в нем transclusion посредством ng-content) отрисовывающий шапку, футер, и место, куда будет помещен второй дочерний компонент с формой редактирования данных пользователя. По сути, в данной схеме будет использован всего один output — от компонента с формой редактирования до корневого личного кабинета. Который после получения новых данных о пользователе тут же отобразит их в лэйауте (в шапке, без всяких ngDoCheck). И в такой схеме я вообще не вижу необходимость какого-то глобального стейта.
которые берутся из сессии (т. е. из storage-а)И чем это отличается от redux-стора?
Не использую общий глобальный стейтА то, что вы вместо одного стораджа предпочитаете другой, абсолютно ничего не меняет.
https://github.com/btroncone/ngrx-store-localstorage
У меня аналогично с первым Angular, с redux. Но mobx прижился. Не так много наступил на грабли, как пугали. Главное не забывать про рекомендации.
в конце то концов, не может же ошибаться целая индустрия?Еще как может :)
А теперь можно посмотреть на https://vuex.vuejs.org и плакать горькими слезами от того, как криво это сделано в redux.
Но redux, как начало очень сильно помог. Но чем дальше, тем больше об будет мешать, вот моё мнение.
VueJS идет в ту же сторону, но можно сделать лучше.
Вам еще не надоело хаить инструменты? Тут ведь все сводиться к делу вкуса: кто-то любит низкоуровневый Redux
, где ты контролируешь весь поток данных, кто-то любит абстракции и магию, не задумываюсь о реализации всего этого. Но с чего это ваши личные предпочтения вдруг делают %инструмент_нейм% ужасным, неэффективным и вообще использование его становится моветон. Есть же альтернативы, так используйте их, зачем писать эти статьи? Я как новичок из них ничего не получу, так как легко могу найти статьи за и против любого инструмента. Все равно придется пробовать самому.
Кому-то нравится электродрель, кому-то молоток и гвозди, а кому-то легче нанять рабочих и вообще не париться.
В первое время при работе с redux льстило, что кругом ФП, чистые функции, иммутабельность. Но потом все
это прошло, и я теперь скромно использую MobX.
Я для себя вывел условную градацию для state менеджмент систем:
- «Большой, могучий enterprise проект», большая команада — Redux
- Небольшая команда, 1-3 человека — можно использовать MobX
- Очень кастомный проект, например графический редактор — RxJS + React State
B все это довольно взаимозаменяемо.
Видите Вы, что команда 5 человек, проект стандартных формочек и таблиц, куча логики под капотом — берете Redux с его жесткими правилами написания кода.
Видите, что вас двое скилованных разработчика — возьмите RxJS и сделайте все по фен шую.
Выбирайте технологию, а не то технология выберет Вас.
Один из вариантов решения проблемы, который к слову работает в продакшне, среднего+ проекта, частично решает вышеописанные проблемы https://github.com/welljs/react-redux-mvc. Может показаться, что с паттерном mvc погорячился, но идея именно в том, чтобы довести фреймворк до состояния близкого к mvc
Принцип прост: компонента react (view) — тупо рисует то, что получила через props от Model. Model — обертка вокруг redux, это то место где формируется грубо говоря json-представление прикрепленной к ней вьюхи, Controller — связывает model и вью, а так же обрабатывает ui-события.
Структура проекта получает следующий вид
/classes - классы для работы с данными
/components - компоненты. тупые умные, не важно. компонуются по принципу - все что нужно компоненте, лежит в ее директории
/layouts - лэйауты - с сайдбаром, без сайдбара, логин, лэндинг ...
/pages - страницы, и компоненты принадлежащие конкретной странице. Также компонуются по принципу, все что нужно лежит в одной директории. Если компонента становится общей для нескольких страниц, обычно достаточно Ctrl+X -> Ctrl+V в папку с компонентами. Умная IDE пути в импортах сама исправит
/redux - экшны для получения данных не имеющих привязки к конкретной вьюхе, например user, agreements, partners
/utils - всякое
Из плюсов: сравнительно легко объяснить принцип работы, дебажить, тестировать, компоновать-перекомпоновывать, шарить компоненты и т.п.
Из минусов: все еще приходится писать немного шаблонного кода, есть небольшие недоделки и не полностью реализован весь замысел
Если кому станет интересно, буду рад почитать отзывы, а если вдруг даже появятся контрибуторы, тогда точно буду знать, что проект годный
сбербанк сделал redux основой своего стека
Если речь о sberbank.ru — они используют платформу Backbase, и там уж «основу» получают как есть. Или речь о мобильных приложениях?
Как раз в тему мой доклад с РИТ-а, где я в том числе рассказываю про проблемы виртуального дома, флакса, асинхронного кода и прочих мейнстримных вещей: https://habrahabr.ru/post/330466/
Дзен не позвонит