Комментарии 159
Ваша реализация combineReducers нарушает главный инвариант redux "ссылочная эквивалентность равносильна отсутствию изменений". Этот инвариант важен, к примеру, для оптимизации рендера.
Например, составной редьюсер должен выглядеть вот так:
function reducer(state, action) {
const next = {
todoState: todoReducer(state.todoState, action),
counterState: counterReducer(state.counterState, action),
}
if (next.todoState === state.todoState && next.counterState === state.counterState)
return state;
else
return next;
}
Этот момент я и правда упустил. Но мой пример настолько вырожденный, что, надеюсь, читатели мне его простят :)
Вообще редакс простой да, мне больше всего понравилось его объяснение как «EE с мидлварами и единственным атомом на конце». Проблема в том что современные джуны не знают ни про мидлвары, ни про EE (event emmiting), ни про атомы.
P.S. в свое время это сделало мой день github.com/reduxjs/redux/commit/9276ff0af6400633d6731b15fed6e70c3561887e
Сейчас используем vue и наконец-то разработка похожа на работу c UI, а не запуск космического корабля.
Лично мое мнение, редакс — это фреймворк для реализации flux паттерна.
Как раз простые на нем писать не надо. Он оправдан когда количество компонент зашкаливает, скажем, за 100.
Как раз нет, с ростом приложения польза от чистого редакса резко падает, и он начинает сильно мешать. Редакс не предоставляет ни средств для изоляции стейта и логики компонент, ни средств для абстрагирования, ни помощи для фиксации какой-то архитектуры.
Кроме того — искаробки не идет никаких методов для управления эффектами и отдельно — запросами. Стандартный подход (по три экшена на запрос) — не масштабируется от слова совсем, т.к. уже в среднего размера приложениях количество этих экшенов начинает измеряться тысячами, а любая нетривиальная логика становится очень сложной для отслеживания — во-первых она размывается по многим местам, во-вторых — ее отслеживание не поддерживается на уровне ide (в отличии от обычных функциональных вызовов).
кроме того — экшоны не composable.
количество этих экшенов начинает измеряться тысячами
Мы уже очень долго обсуждали это ранее на хабре (это невозможно забыть) :-)
Это проблема именно вашего подхода к redux (а точнее к actionCreator-ам). Там где у всех 10 универсальных экшнов, у вас целая 1000 копипастных с бизнес-логикой.
Это проблема именно вашего подхода к redux (а точнее к actionCreator-ам). Там где у всех 10 универсальных экшнов
Я же специально указал, что это про "чистый редакс" и "Стандартный подход (по три экшена на запрос)".
Если же использовать редакс в качестве набора примитивов для построения своей системы стейт-менеджмента (где будет и изоляция, и архитектура, и абстракции и нормальная работа с асинхронщиной), то, конечно, это решаемо.
Ну и да, судя по гитхабам (да и по тому что я видел в своей практике) — как раз все и плодят эти десятки экшенов, которые превращаются в сотни (и, в перспективе — в тысячи), а исключение скорее как раз, когда над редаксом делают свою кастомную надстройку с генераторами экшенов/редьюсеров/саг и т.д.
Это и есть набор примитивов.
Именно про это я и говорил. К сожалению, большинство этого не понимает и использует этот набор примитивов как есть.
О чем и статья.
Нет, статья совсем не о том.
Может быть потому что большинству просто не надо его использовать, как его нет?
Такие варианты тоже есть. Но мы все же говорим не о микро-проектах. Там обычно редакс (или какой-то аналог) нужон.
Там пониже есть список клиентов, которые используют редакс с ангулар.
Я сам использовал (и продолджаю использовать) редакс с ангуляром.
Я не знаю в какой вселенной вы живете, пишите на чистых функциях большие проекты
Кто говорил про большие проекты на чистых функциях? Я говорил как раз про редакс (или аналог) на больших проектах. Вы невнимательно читаете.
Обычные функции, представьте себе. Они и composable, и нормально ищутся по коду, имеют нативные средства для группировки/изоляции и вообще имеют полную поддержку со стороны всей языковой инфраструктуры напрямую.
Вообще, при работе с редаксом следует заменить с самого начала одну главную вещь — если вы создали action, то вы потом его диспатчите в стор. Ни для чего другого action'ы не используются, нет других сценариев. С-но, вместо того, чтобы иметь ф-и, которые создают экшены, вы можете создавать ф-и, которые создают экшон и сразу его диспатчат в стор. И потом работать с этими ф-ми вместо экшенов и экшен криэйторов. Это сразу все упрощает и делает на порядок удобнее.
Вопрос же в том что у вас не получается, это не значит что редакс не подходит для больших проектов.
Я вроде уже говорил — он подходит для больших проектов, но не в чистом виде. Потому что в чистом виде — у вас тысячи экшенов, хаотично разбросанных, с размазанной логикой.
редусеры — эпики — саги.
Саги не имеют ничего общего с редаксом, там код совершенно независимый. Можно подключить саги к любой другой системе контроля эффектов.
Ну и потом, кто вообще вас заставляет пользоваться экшен криейторами, сделайте фактори и диспатчте на здоровье один акшен и свои фунции в пейлоаде, если так хочется, в чем проблема?
Так я же про это и говорю — не использовать чистый редакс, а использовать свои абстракции поверх него. С чем вы спорите?
Экшен криеторы это не редакс, никто не заставляет именно так создавать и диспетчить.
Это стандартные соглашения работы с редаксом. Если вы не используете экшон криейторы — значит, у вас своя обертка над редаксом. О чем я и говорил — использовать не чистый редакс, а кастомные обертки.
Я, видимо, плохо понимаю что такое чистый редакс, стандартные соглашения работы с ним, и где он кончается и начинается кастомная обертка.
Видимо, да.
Проблема не в глобальном сторе, а в глобально мутабельном сторе.
Стор реакта мутабельный, т.к. стейт хранится в мутабельной переменной внутри этого стора.
Выше в статье даже код есть, как этот стор обновляется:
dispatch: action => { state = reducer(state, action) },
Видите присваивание?
Стор у редакса (я надеюсь вы о редаксе, а не о реакте) такой, каким вы его сделаете.
Нет, в редаксе есть конкретная реализация стора, состояние которого изменяется при помощи мутабельного вызова store.dispatch(action);
И ладно бы был какой-то хитрый способ заменить эту реализацию на иммутабельный стор, не выкидывая весь редакс — я бы тогда был вынужден признать, что вы, пусть и не формально, но правы.
Такого способа, однако, нет.
с какогото перепугу решили что там описан эталонный чистый редакс и его бест практисез
Окей, теперь буду знать, что на https://redux.js.org/ не описан эталонный редакс и его бест практисез.
А где описан, не подскажете? Буду искренне благодарен за ссылочку.
везде, где присваивание, там мутация?
Везде, где присваивается другой объект :)
Это не мутация самого обьекта.
Ну так стору новый стор и не присваивается. Стор остается тем же самым, но внутри него меняется стейт на новый. То есть, у вас есть объект store = { state: initial } и вы делаете store.state = reducer(store.state, action);
Типичная мутабельная операция. Каноничная прям я бы даже сказал.
Это зависит от того, что у вас в редюсере. Бест практис — строить редюсер как фабрику для нового стора.
Вы путаете стейт и стор. Стор — это та штука, на которой вы вызываете dispatch, и которая содержит стейт. Стейт — это поле внутри стора. Редьюсер возвращает новый стейт, который заменяет в сторе старый.
Так вот в редаксе:
- стор — мутабельный
- эту мутабельность никак не выпилить
- та недоиммутабельность, что таки есть, ничего не дает самому программисту (в сравнении с той, которая, например, в хаскеле). совершенно без разницы, с точки зрения качества кода и удобства поддержки, поменяете ли вы стейт мутабельно или вернете новый. Возврат нового нужен только для того, чтобы можно было использовать нативные средства оптимизации реакта — более ни за чем.
Т.о. редакс — это как раз канонический пример мутабельного глобального объекта в его худшем проявлении. Тут не может быть разных точек зрения, потому что это просто факт, как он есть.
Никому не дает, потому что это строго эквивалентный код. Его можно прогнать через плагин и автоматически преобразовать даже из одного вида к другому, без каких-то особенных проблем.
Да пожалуйста. Это ведь никак не отменяет того, что опасность глобальных мутабельных объектов подтверждена уже почти полувековой (!) практикой.
У многих современных фронтендеров тогда еще папа не родился — а сообщество программистов уже знало, что это все зло, и знание это было записано кровью павших :)
Ничего что обьекты window и document — глобальны и мутабельны?
Конечно же, чего, почему вдруг нет?
и ничего лучше не придумано
Просто жс был сделан на коленке за пару дней для двадцатистрочных скриптиков, и никто не предлагал, что его будут использовать когда-то как general purpose language. А потом стало поздно. Вот и приходится людям колоться, но жрать кактус.
А так-то придумано миллион разного чего.
та недоиммутабельность, что таки есть, ничего не дает самому программисту
Эта "недоиммутабельность" даёт возможность в принципе как-то использовать оптимизации реакта. Не понимаю почему вы считаете что она не нужна программисту.
Эта "недоиммутабельность" даёт возможность в принципе как-то использовать оптимизации реакта. Не понимаю почему вы считаете что она не нужна программисту.
Она дает программисту редакса, которому не надо ничего придумывать для оптимизации. Но не дает программисту, который редакс использует. Т.к. с его стороны в любом случае все будет оптимизировано, при любом подходе.
Да я уже понял, пишите на обычных функциях свой велосипед каждый раз
Итак имеем:
— Глобальный стор (обычно все глобальное — плохо, а тут вдруг — хорошо).
— Кучу экшенов где action.type уникален в пределах приложения.
— Кучу action creator-ов c названием, в большинстве случаев, таким же как action.type, записанным в другом регистре.
— reducer-ы — по сути обычный switch case.
Теоретически, для уменьшения бойлерплейта вы даже использовали какую-нибудь свистоперделку.
Вопрос:
Где в этом хозяйстве архитектура, абстракции и изоляции и чем оно координально лучше, чем обычные методы хранилища? ЗЫ: Методы хотя бы можно комбинировать в отличии от словоблудия внутри switch case.
Не ВСЕ глобальное плохо. Существует миллион глобальных вещей в любой платформе.
Что-то не могу придумать/вспомнить ни одного случая, когда при наличии выбора глобальный/не глобальный выбор был бы в пользу первого. Может подскажите.
И так и нет ответа при этом, что же лучше, если не ванилла самописный велосипед, который конечно же лучше, потому что свой. Спорить даже лень.
Про ванилу в командной разработке вы конечно правы, но мне не понятно каким образом люди приходят к выводу что именно редакс является лучшим выбором. По моим наблюдениям обычно происходит приблизительно так:
Сеогдня: О! Экшены, экшен-криэйторы, редьюсеры… это круто! Используем их.
Завтра: Ээээ. Нам срочно нужна библиотека, которая уменьшит кол-во бойлерплейта и избавит нас от всех этих бессмысленных экшенов, экшен-криэйтеров и т.д.
Сегодня: О! Single source of truth бенефит (так ведь это модно называть?) это круто. Он нужен нам!
Завтра: Ээээ. А давайте использовать какой-нибудь reselect чтобы хоть как-то разбить это монолитное говно на части.
Самое странное, что даже наступив на грабли, они продолжают одновременно придерживать обеих точек зрения (Single source of truth — отличная идея, но reselect мы все-таки используем). Шизой попахивает КМК.
А вот те разработчики, которые прочитали хотя бы GOF, с порога понимают как все устроено и к чему это безумие приведет, но к сожалению таких почему-то меньшинство.
ЗЫ: Я не про вас лично, а про frontend в целом.
=Шизой попахивает КМК.= Это не шиза, это нормальное развитие. Не придумали еще ничего идеального, ни в смысле платформы, ни в смысле методологии/подхода в целом, вот и мечутся.
Не придумали еще ничего идеального, ни в смысле платформы, ни в смысле методологии/подхода в целом, вот и мечутся.
Ну как сказать. По факту реакт и экосистема — это то, как писали на пхп в нулевых, то есть все это уже старо как мир, 15 лет назад обсосано, все эти проблемы исследованы, решения (в современных пых-пых фреймворках) найдены и классифицированы. Причем взяты они еще из разработки 90-х под десктоп.
Просто js только-только начался как полноценный язык с полноценной экосистемой, вот и проходит соответствующий цикл развития, ну программисты вынуждены быть контрамортами и жить в прошлом на четверть века назад :)
Это не шиза, это нормальное развитиеЗнаем мы такое развитие IRL. Их впоследствии неандертальцами обозвали ;)
1. редьюсеры чистые функции (а значит мы не можем использовать внутри грязные функции типа ajax запросов)
2. состояние иммьютебельно и едино (библиотеки и combineReducers конечно помогают, но код читать и отлаживать довольно тяжело)
2. состояние иммьютебельно и едино (библиотеки и combineReducers конечно помогают, но код читать и отлаживать довольно тяжело)C github.com/mweststrate/immer можно менять стейт императивно, как-будто напрямую, а библиоетка сделают грязную работу за вас обрабатывая прокси дифф.
Иммутабельность помимо прочего позволяет выполнять сравнение по ссылке при ченж детекшене, что при правильном использовани позволяет избежать лишних отрисовок и в целом значительно увеличить производительность.
— «но код читать и отлаживать довольно тяжело» — мутабельный код отлаживать на порядок тяжелее. Я помню этот ужас, когда логируешь стейт в Alt.js, а он отображается текущим в консоле, а не на момент лога, это совсем не явно, потом приходилось глубокую копию делать, что бы продебажить нормально. Я бы и без редакса использовать иммутабельность — она не такая дорогая, но дебажить ее в разы проще.
> Разделение ответственности — это прекрасно.
Разделение ответственности по функционалу прекрасно, а что прекрасного в разделение логически цельной функции на несколько? В чем преимущество здесь делать запрос отдельно?
вот тоже, пока что только слышал восторженные отзывы, но когда сам пытался писать на react/redux то мозг вскипал на простых казалось бы штуках.
изначально сделать на сторе
Вот меня напрягяет когда простые вещи, имеющие отношение только к V начинают запихивать в store.
Нам нужен сайдбар с одним единственным свойством collapsed. Для этого нам нужно (если следовать документации к redux):
1. Придумать константу с названием действия.
2. Написать ничего не делающий actionCreator.
3. Добавить в reducer обработку действия.
4. Написать компонент-контейнер подключающий Sidebar к Redux.
И всё это в разных файлах. Пример конечно несколько утрированный, но это же программирование ради программирования.
Нам нужен сайдбар с одним единственным свойством collapsed.Бывает нужно например переключать это поле collapsed из самых разных мест или завязать на это же значение еще какие-то отрисовки кроме самого сайдбара и здесь центральный стор помогает очень. Особено удобно когда стор observable.
Константы на экшены не нужныВроде бы да, но нет. В большом приложении будет очень много actionCreatoro-в и, рано или поздно, захочется разделить их на насколько файлов. При добавлении нового действия придется просмотреть все эти файлы и убедиться что действий с таким же action.type не существует.
Бывает нужно например переключать это поле collapsed из самых разных мест...А в каком-нибудь, не любимым всеми, ExtJS это бы выглядело приблизительно так:
function onTriggerClick() {this.down('sidebar').toggle()}
И больше ничего.Не использовал unionize, но выглядит не очень читаемо КМК.
Дело не только в читаемости, но в поддежке TS. Есть много аналогов, но именнно эта библиотека сама по себе небольшая, но гибкая. Делать отдельные файлы на каждый экшен это действительно плодить много бойлерплейта. Мне удобнее объединять экшены в unionize бандлы группируя их по назначению. Правда бибиотека пока что не поддеривает префиксы для бандлов, но ее можно форкнуть и добавить.
function onTriggerClick() {this.down('sidebar').toggle()}Полагаю вызов down ресолвит вложенный компонент и явно вызывает у компонента метод toggle. Но это ведь жесткая привязка к структуре дерева компонентов. Кроме того строгую типизацию такого сделать будет сложновато. Ну и в скорости ресолвинга не уверен.
Делать отдельные файлы на каждый экшенНу не так же радикально. Логически разбить по несколько экшенов в файл (в каком-нибудь TODO это действия относящиеся к пользователю и действия относящиеся к задачам).
Полагаю вызов down ресолвит вложенный компонентЭто только для примера. Перемещаться по дереву можно было в любом направлении.
тк со временем стало сложно развивать и поддерживать штуковины где состояние изолированно хранилось в самих компонентах или где-то в разбросанных сервисах.
Это верно только для очень маленьких приложений. Вообще же изоляция стейта ведет к снижению coupling, то есть полезна почти всегда. Вынос стейта в глобальную переменную — это всегда грязное решение, которое удобно в краткосрочной перспективе, но ведет к драматическим последствиям в долгой. Просто на долгую перспективу во фронетенде в 2019 обычно не работают.
Правильное решение в случае проблем с взаимодействием модулей — переработка связей между модулями.
Действительно ли нужны иммьютабельные состоянияДа это больше похоже на костыль для реакта ибо
— передавая в компоненты реакта мутабельные объекты вы не сможете нормально реализовать PureComponent.
— Это приведет к реконсилированию (так оно кажется называется) практически всей ветки компонентов.
— А это приведет к тормозам в приложениях сложнее HelloWorld.
— Использовать иммутабельные данные, лучше простых типов (примитивы + объект и массив), тогда можно просто сравнивать по ссылке. Вывод: дешевое сравнение.
— Делать deep checking. Вывод: дорогое сравнение, тормоза.
— Использовать обертку с методами get/set или что-то вроде прокси или Object.defineProperty и внутри творить магию. Вывод: магия, вероятно большее потребление памяти, хакнутая структура данные и тд.
Именно один глобальный стор и дает single source of truth бенефит.
один store, оповещающий все компоненты
Всё так, но мне кажется, тут важно отметить, что это фишка react-redux. И никто нас не сковывает в этом деле. Можно нарисовать свою древовидную систему обновлений, или воспользоваться уже какой-нибудь готовой.
Т.е. это не проблема подхода как такового. Это проблема конкретного решения.
вы знаете другой способ понять изменилось ли свойство объектаА вы ответьте себе на вопрос зачем вам понимать что свойство изменилось? и сразу станет понятно
Причем тут стор, который кого-то там оповещает
Мы ведь по прежнему говорим про иммутабельность применительно к редаксу?
Нет, вы сказали про иммутабильность по отношению к реакт:
Иммутабельность нужна в первую очередь реакту.
Redux независимая библиотека, которую используют далеко не только в React проектах.
А вы ответьте себе на вопрос зачем вам понимать что свойство изменилось? и сразу станет понятно
Очевидно, чтобы применить это изменение к DOM. А вы знаете иной способ синхронизации стейта и его представления в DOM дереве?
Redux независимая библиотека, которую используют далеко не только в React проектах.Если кто-то притащил эту либу в проект не связанный с реактом, то это совсем не означает, что она является оптимальным выбором. Просто у многих frontend-разработчиков на текущий момент наблюдается эффект Даннига-Крюгера совместно с тяжелейшим религиозным повреждением головы. Для эксперимента попробуйте порекомендовать С++/JAVA-разработчику использовать подходы из редакс в своей повседневной деятельности. Только наденьте средства индивидуальной защиты, поскольку его ответ будет находится где-то в интервале между плевком в лицо и нанесением тяжких телесных повреждений.
Очевидно, чтобы применить это изменение к DOM. А вы знаете иной способ синхронизации стейта и его представления в DOM дереве?
Без обид, но вам это очевидно по причинам, указанным под спойлером. Объясню простым примером:
1. Подход редакс.
— Есть стор с двумя свойствами a и b.
— Есть 2 компонента: A (использует свойство a) и B (использует свойство b).
— Изменяем свойство a.
— Стор оповещает компоненты A и B.
— Компонент А понимает, что свойство a изменилось и рендерится.
— Компонент B понимает, что свойство b не изменилось и не рендерится.
2. Подход не редакс.
— Есть 2 стора в одном значение a в другом значение b.
— Есть 2 компонента: A (использует свойство a) и B (использует свойство b).
— Изменяем свойство a.
… дальше сами можете догадаться.
Банальщину же обсуждаем.
На самом деле так можно придти к тому что у вас вообще все скалярные значения будут отдельными сторами. И по сути вы переизобретёте observer :) (redux это по сути 1 observable c заморочками)
Однако ваши примеры отражают не понимание вопроса. Вот мой пример:
— Есть объект с данными а N-ой вложенности.
— Есть один компонент А, который его использует.
— Изменяется свойство объекта а на неком уровне вложенности (пусть будет a.b.c).
— Как нам оптимально применить это изменение в DOM?
Ту банальщину, которую вы написали давайте обсуждать не будем.
Неужели нужно подписаться на изменения объектов a, a.b и a.b.c?
Здорово! Нам наверное не расскажете как?
Пока нам не завезли Object.observe (а его вероятно и не завезут), вы никак в JS не можете понять изменился ли объект. Используя иммутабельность можно хотя бы частично решить эту проблему.
Ну и напишите что-нибудь вместо трех точек из моего предыдущего сообщения. А то у меня начинают закрадываться подозрения.
Вы написали, что «иммутабельность нужна в первую очередь реакту». Я вот и пытаюсь понять, как же вы тогда определеяете что объект был изменен? Целый тред развели из-за такого простого вопроса.
Фрагмент вашего предыдущего собщения:
вы знаете другой способ понять изменилось ли свойство объекта
Фрагмент моего предыдущего сообщения (дополненный):
1. Подход редакс.
— Есть стор с двумя свойствами a и b.
— Есть 2 компонента: A (использует свойство a) и B (использует свойство b).
— Изменяем свойство a.
— Стор оповещает компоненты A и B.
— Компонент А понимает, что свойство a изменилось и рендерится.
— Компонент B понимает, что свойство b не изменилось и не рендерится.
2. Подход не редакс.
— Есть 2 стора в одном значение a в другом значение b.
— Есть 2 компонента: A (использует свойство a) и B (использует свойство b).
— Изменяем свойство a.
— Стор со значеним а оповещает компонент А.
— Компонент A рендерится.
Где во втором подходе вы увидели слово «понимает»? Компонентам плевать на изменение данных, которые не имеют к ним отношения.
Ваш пример с a.b.c:
Если вашему компоненту нужно свойство c, но вы зачем-то передаете в него a, то… ну сами делайте выводы насколько это нормально. Так что приведите какой-нибудь реальный кейс для вашего примера. Но поскольку я уже знаю что вы напишете то оставлю это здесь:
U2FsdGVkX19PiLnKL6SHSQNkAiMWCO56yBnZnjtr8d/S5djFMwZ9Xy7EH5K0x/ug
kjfZYWkQU+iAp7qpacJXLYQgzka4Qw66ADNuCLsEoncKeLm/ohPEGdbbKfL6Y09L
Если вашему компоненту нужно свойство c,
Я разве где-то писал что ему нужно свойство c? Ему нужен весь объект a. Если писать вашим языком — компонент A использует объект a.
Вот вам кейс:
<UserProfile user={user} settings={settings} />
Оба пропса — это объекты.
Воду льете вы. Говорите о каких-то компонентах, вне какого-либо контекста. Я же задал вам очень простой вопрос — знаете ли вы иной способ, кроме иммутабельности, чтобы эффективно определить, был ли изменен объект или нет? Если знаете, мне было бы интересно это узнать. Если нет, тогда иммутабельность нужна далеко не только Реакту.
Кстати если вложенность компонентов достаточно большая и свойства из user и settings испольльзуются только в самых глубоколежащих компонентах, то только что вы сделали все промежуточные уровни зависимыми от свойств, которые никакого отношения к ним не имеют. Так-то.
Я, наивно предполагаю, что внутри UserProfile есть еще компоненты, внутри которых есть еще компоненты и т.д в которых вы собираетесь использовать свойства из user и settings и именно здесь вам и нужна иммутабельность.
Да, видимо вы наивны. Я вам привел полный пример. У меня очень простой профиль пользователя и мне подкомпоненты не нужны там.
Так как будете определять, что свойства user или settings изменились?
Ой! Это ведь дерьмовый подход реакта прокидывать пачки свойств через все дерево компонентов.
Я вам про React вообще не пишу. И про Redux не пишу. Причем тут они? Даже про компоненты стали писать вы.
Еще раз для самых наивных: Как эффективно определить изменился ли объект, без иммутабельности в Javascript?
Это краеугольный вопрос для многих других операций, кроме синхронизации стейта и DOM. Или вы наивно предполагаете, что безотносительно к React/Redux, вам никогда не нужно определять изменился ли объект JS?
Я вам про React вообще не пишу. И про Redux не пишу.Ну почитайте что-ли первый комментарий или комментарий сразу перед этим и подумайте про какую иммутабельность и где идет речь.
Ну и если же мы про иммутабельность во фронтенде, то:
Продолжайте натягивать сову на глобус.
Ну почитайте что-ли первый комментарий или комментарий сразу перед этим и подумайте про какую иммутабельность и где идет речь.
А причем тут первый комментарий? Я вам 5 раз написал, что мой вопрос про иммутабельность в JS в целом, а не относительно к Redux. Когда вы первый раз не поняли, еще ладно, но потом то чего не ответить на простой вопрос?
Ну и если же мы про иммутабельность во фронтенде, то:
Цитата хорошая, но не отражает конкретно вашего понимания применительно к особенностям JS. Делаю вывод что его просто нет. В этом случае, пожалуй, хватить вас мучать. Вижу что ваша «сова» устала натягиваться.
У меня другой вопрос — а зачем вообще нужен редакс?
Редакс — это просто набор примитивов, из которых можно собрать решение для менеджмента стейта вашей системы (для применения сам по себе, без обработки напильником, редакс никогда не предназначался). Безусловно, можно все эти по-5-строчек написать и самому, и каждый раз переписывать, или иметь свой набор и из проекта в проект тянуть. Но поскольку реалии современного фронтенда таковы, что команды/проекты меняются чуть не по нескольку раз в год — желательно использовать библиотеку, которую все знают.
Редакс предлагает очень привлекательные принципы rajdee.gitbooks.io/redux-in-russian/content/docs/introduction/ThreePrinciples.html. Но в жизни оказывается много бойлерплейта, ломания головы над правильной структурой стейта, странные костыли и т.д.
Но в жизни оказывается много бойлерплейта, ломания головы над правильной структурой стейта, странные костыли и т.д.
Так я же вам и говорю — бойлерплейт возникает от того, что используются низкоуровневые примитивы сами по себе. Если оборачивать их в абстракции — тогда он уходит.
А ломания головы над структурой стейта будут всегда, это сложная задача и не может быть магической кнопки, которая за вас ее решит.
Там бойлерплейта в разы меньше, стейт не надо так продумывать, о чистоте обработчиков (редьюсеров) заботиться, об иммьютабельности и т.д.
И вопрос заключается в том перекрывают ли плюсы редакса те неудобства, что он создает по сравнению с другими подходами.
Я имел ввиду redux по сравнению с другими подходами к программирования UI (например Angular, Vue или из других областей WinForms, WPF, да даже другие реализации Flux)
Ну и в других подходах все ровно то же самое.
Там бойлерплейта в разы меньше, стейт не надо так продумывать, о чистоте обработчиков (редьюсеров) заботиться, об иммьютабельности и т.д.
Если вы прикрутите редакс к ангуляру или вуе — точно столько же будет бойлерплейта, очевидно. Вы просто сравниваете систему для контроля стейта с ее отсутствием. А надо сравнивать одну с другой. Когда ваш проект начнет расти — вы все равно какую-то систему такую прикрутите, вопрос лишь в том — будет ли она вашим велосипедом или известным всем решением.
Всё решаемо. У меня довольно тесно переплетённый граф данных сейчас в приложении (редактор расписаний в школе, очень много взаимосвязей). Поэтому есть деревья селекторов (это такие кеш-функции для просчёта чего-угодно на основе store-данных). Всё "летает", несмотря на то, что некоторые штуки вычисляются на лету, скажем, при drag-n-drop. Но приходится использовать обильно мозг при построении архитектуры этих самых селекторов и работы со store-ом в целом. Всё, разумеется, иммутабельно. В принципе удобно. И очень легко дебажится, работает очень предсказуемо. Тут самое главное в архитектуре стора не ошибиться сильно. Любой серьёзный рефакторинг в этом деле — боль. Забыл добавить, данные в сторе нормализованы. Плюс используются мемоизация на основе weakMemoze, включая вложенные weakMemoize-ы. А для reducer-ов пишется "аля-мутабельный" код с proxy (т.е. по факту иммутабельный). В общем никакого криминала, когда уже набил руку на предыдущих проектах.
Вначале я попробовал реализовать этот же проект на Vue. Я столкнулся с очень серьёзной проблемой в tracking dependencies механизме и в итоге отказался от Vue. Думаю что для проектов с большим графом данных я Vue больше выбирать не буду. Были варианты остаться и использовать вместо computed скажем watchers, или вообще притащить туда что-нибудь типа rxJS… но зачем мне тогда Vue? :)
Я столкнулся с очень серьёзной проблемой в tracking dependencies механизме и в итоге отказался от Vue.Если обобщить во Vue слишком много неподконтрольной «магии»?
Попробую кратко описать ту проблему:
- есть 5000+ observable values
- есть computed A, который их перебирает
- есть computed B, который делает "A + 1"
Что происходит в случае KnockoutJS, если A уже был просчитан ранее, а B нет?
- knockout дёргает закешированное значение от A
- knockout связывает напрямую B с A, чтобы работала реактивная магия
Что происходит в случае с Vue?
- vue дёргает закешированное значение от А
- vue связывает B с A.dependecies.*, коих 5000
Итого O(1) vs O(n). Такое происходит с любыми computed какие только в проекте есть и завязаны на эти A. А у меня дерево таких. Плюс в моём случае это всё рендерится в таблице, где может быть до 160 ячеек разом. И каждая ячейка помимо render-method-computed может содержать ещё всякие другие computed. И все они непременно дёрнут те самые 5000. Думаю излишне говорить, что это начинает просто нещатно тормозить даже на самых малых выборках. А проект предполагал куда более сложные связи.
В итоге я заменил Vuex на Redux, Vue\Vuex Computed на Reselect, Vue Components на React Components. По сути практически та же кодовая база, но всё работает молниеносно. Ну и очевиднее в разы :) И ещё есть куда лихо заоптимизировать если будет мало.
За точный механизм работы Vue и Knockout не ручаюсь. Уже успел всё подзабыть. Пишу по старой памяти. Если чего нахимичил — прошу сильно не пинать :)
Ответ прост: дедлайны наступали на пятки. Взял то, что умею хорошо готовить. В режиме copy-paste понадёргал с прошлых проектов что надо и просто методично шаг-за-шагом переписал. Интерес к mobX питаю, но пока ещё с ним не работал. Уверен, там как и везде, есть свои тонкости. А я итак исчерпал все резервы времени к тому времени )
В mobx-е описанной вами проблемы с компьютедами нет. Там компьютеды не только вычисляются иерархически но и решают проблему "ромба" (дублирующие вычисления при ромбовидных зависимостях) и условных подписок (когда зависимости меняются во время вычисления и могут быть лишние запуски). В общем используемый алгоритм гарантирует что при любых ромбовидных или условных зависимостях запуск компьютеда произойдет только один раз в цепочке вычислений (и также дополнительно можно заврапить в транзакцию чтобы множественные изменения внутри функции схлопнулись в один запуск цепочки вычислений). Подробнее про алгоритм можете почитать в этой статье и посмотреть примерную реализацию тут или тут или с дополнительными оптимизациями тут
Понятно что все решаемо, непонятно зачем колоться об этот кактус если все можно сделать проще и красивее.
Не ну если знать как и уметь, то какие проблемы? :) Скажем я пока не научился решать такие вещи эффективно используя observer и без глобального стейта. Какие-то мутные путанные клубки получаются. Технический долг неистово накапливается. Тут практика нужна. Понятно что существуют тысячи вариаций как сделать это хорошо. Но вот без опыта они не приходят.
По redux/flux есть много учебных материалов (скажем "пятиминутка React"), которые показывают и рассказывают, что да как. Спустя 4-5 проектов на Redux мне всё ещё кажется, что с ним всё довольно очевидно делается. Не приходится ломать голову. Этот деревянный простой как грабли подход сам вынуждает делать удобную архитектуру.
ИМХО
Примерно так:
- Нормализация данных в с store
- redux-thunk | redux-saga | custom middleware | 100500 других решений
- DRY
В любом случае решения без внешнего store и с observable будут проще. Т.к. всё расположено прямо по месту. Flux заставляет разделять виды действий\команды, их реализацию и места применения. Redux ещё рекомендует держать конечные View предельно тупыми. Это всё неизбежно приводит к разбуханию кода. Но в средне и долгосрочной перспективе это сильно помогает. Это как строгая типизация — добровольные кандалы, которые дают определённые преимущества. Вопрос лишь в том, дают ли они именно вам больше, чем отнимают.
Определить в одном месте функцию-обработчик — это, по вашему, следить самостоятельно? А Redux стало быть магическим образом значения привязывает и для этого ничего делать не надо? Ох.
Представьте себе приложение со сложным интерфейсом и огромным количеством возможных действий пользователя, построенное на обсерверах, которое при каждом действии этого пользователя оповещает все компоненты на странице (даже те компоненты, которые текущее изменение состояния не затрагивает).
Как бы вы оценили архитектуру такого приложения?
PS не пишу именно о Redux, но в целом о подходе.
Тут главная засада — не допустить через чур большого кол-ва connect-утых компонент. Т.к. mapStateToProps будут вызываться для всех из них всегда. Приходится включать голову и как-минимум группировать такие вещи.
В двух словах примерно так и есть. Просто часто возникает соблазн на уровнях ниже тоже понаклепать своих собственных контейнеров. Далеко не всегда это приемлемый путь. А проброс всего чего надо в достаточно глубоком дереве — штука тоже… неудобная. Частично может выручить context. В любом случае, когда мы имеем достаточно сложный UI с сотнями и тысячами всяких элементов, тогда нужно хорошо продумывать эти вот сочленения со store-ом. Иначе оно высоко и далеко не полетит.
Узким местом является асимптотика такого решения. То что могло отнимать O(1) с тяжёлой константой (observer), отнимает O(n) с лёгкой константой (immutable + shallow comparions). Вопрос в N (число коннектов). Сами shallowComparison то быстрые. Но это не отменяет того, что:
- когда 99% времени библиотека считает что-нибудь за-зря, мы как минимум съедаем аккумуляторы конечных устройств
- всегда есть очень медленные девайсы, и узкие случаи, которыми мы пренебрегли, не заметили и т.д., которые могут выродиться во что-то ну совсем несуразное
- не всегда это можно быстро и легко исправить.
Т.е. я бы всё же стеснялся использовать connect всякий раз когда вижу какие-нибудь вещи в циклах и предполагаю там 50+ элементов. По правде я "стесняюсь" и при куда меньших масштабах. Но это уже моя паранойя )
Если аккуратно перенести всю концепцию Elm Architecture на JS, а не ее треть, и аккуратно продумать как удобно положить на JS — была бы хорошая, хотя и довольно нишевая, вещь. Но Денис написал 7 строк кода, и побежал пиарить это на весь мир. А нам теперь каждому первому джуну объяснять почему мы не пишем на Redux-е.
Юзаем Apollo + удобняшки.
— Apollo. Замечательно работает для UI, где мы больше показываем, чем редактируем (как тот же хабр). На чтение — Apollo пашет просто отлично, код чисто декларативный. Плюс GraphQL очень сильно помогает на бекенде — даже больше чем на фронте. Мутации — тоже в целом ок, но до момента когда начинается всякий CRUD, формы, optimistic updates.
— самодельные формы. Приделываются к Apollo, и дополняют его. На момент редактирования, стейт закидывается в компонент Form, который организует data binding через линзы, валидацию, всякие undo/redo/cancel. По save — стейт кидается в мутацию, и погнали дальше через Apollo, а сама форма помирает.
— какой-то временный стейт типа фолдинга — мы часто делаем по-классике, просто через this.state. Часто храним весь viewstate скрина прям в URL — скажем, состояние фильтров на каталогах.
Вместе эти штуки уже закрывают 90% наших нужд.
Есть несколько хитрых и сложных скринов, уровня того же Trello. Такие случаи нормально на Apollo не ложатся. Там мы велосипедим что-то типа Redux, но не совсем. Скажем, на одном таком скрине, UI синхронно работает с immutable-стейтом, а штука сбоку и в фоне — асинхронно синхронизирует этот стейт с сервером.
Сам участвовал в нескольких проектах на Redux, MobX и даже на mobx-state-tree. Теперь вот посматриваю в сторону GraphQL. Но тут как всегда в экосистеме React — две конкурирующие технологии =(
У нас изначально не было Babel (был голый TypeScript), а Relay сразу его требует — чтобы компилировать запросы. Поэтому мы его сразу отсекли. Сейчас я думаю что лишняя строгость в схемах — может быть и плюс, а Babel все равно теперь нужен — даже с TypeScript-ом. Так что может Relay и ок, но нам уже смысла нет менять коней.
А вообще, про Apollo/Relay/Redux/MobX — надо понимать что нет серебрянной пули для state management-а. Нельзя выбрать одно решение и использовать исключительно его. Даже на уровне одного приложения может быть 9 скринов идеально ложащихся на Apollo; и один скрин на который Apollo не лезет. Но это же не значит что для тех 9-ти скринов надо писать 100500 редьюсеров и врукопашную менеджить кеш и isLoading-состояния.
Пара уроков там посвящено тому как реализовать свой redux
Только вот жаль, это не очень прояснило обычные паттерны его использования.
} catch(e){}Как-то такое не выглядит готовым для продакшена.
Листенеры в таком виде не особенно полезны, было бы добно подписаться на изменение определенного участка стора. Можно сделать гибкую штуку на BehaviorSubject из RxJS и кода тоже будет мало не считая конечно саму библиотеку RxJS.
О том что возможности TypeScript совсем не используются писать не буду в деталях, тема широкая. Но в целом использование нетипизированных Object и Function нивелирует практически все преимущества TS, лучше тогда уже просто JS взять.
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
Написано не это, тут catch вообще нет — следовательно и перехвата ошибки нет. Только гарантированное действие в finally
Сейчас уже можно выбрать более адекватные инструменты. Мне лично очень нравится vue (и vuex — если нужен централизованный стейт). Он очень похож на react+mobx. Мне кажется, таким и должен был быть react изначально.
И, ссу… ка, оно работает…
Все гениальное просто -)
Redux. Простой как грабли