Comments 33
Стоит заметить Мы убрали константы.
Ну константы в `actions` у вас никуда не делись.
Плюс, данный подход просто переложил проблему из одного места в другое. Вместо того что бы иметь большой `switch` с обработчиками в файле `reducer`, вы будете иметь уйму обработчиков в файле `actions`, в последствие, по мере разрастания проекта, по-прежнему придется пользоваться тем же поиском.
У себя в проекты, мы пришли к тому, что бы стараться максимально комбинировать редьюсеры, если есть такая возможность, что бы отдельный редьюсер был минимально возможным.
Наблюдая выше в примере редьюсера комбинированное использование как Object.assign
так и спред оператора, подозреваю что и остальная часть проекта страдает от подобной неконсистентности, которая и вызывает вашу так называемую "боль"
поиск — нужно все время нажимать Ctrl + F причем глобально Ctrl + Shift + FИспользуйте TypeScript.
не видно сразу от куда ноги растут. Вытекает из пункта выше.Используйте TypeScript.
нет, это всего мало, так у меня еще весь проект пронизывают константы. Нет я не против констант но зачем? Тем более если их использовать вместе с вложенностью как в примере да если еще их конкатенировать из нескольких то это вообще ад навигации.Используйте github.com/pelotom/unionize.
логика размазана. В одном месте действия в другом обработка этих действий в третьем (опционально) константы которые нужны только тем двум.Используйте github.com/pelotom/unionize.
Вот ради этого мы вынесли логику отвечающую за передачу данных стору. Reducer остался обеспечивать работу всего механизма. И он должен делать это хорошо не отвлекаясь на вещи его не касающиеся. А нам остается только наблюдать порядок в том от куда растут ноги и если надо то быстро найти и исправить или дополнить.Очень недальновидно прилепить реализацию ридьюсеров к экшенам:
— В приложении может быть много разных ридьюсеров на тот же самый тип экшена. Допустим я послал экшен из модуля1, а модуль2 и модуль4 будут по-своему его обрабатывать собственными ридьюсерами, плюс lazy модуль5 когда загрузится тоже начнет этот экшен обрабатывать. Пример утрирован, но что-то такое может существовать. Исходите из посыла что экшен это событие с пейлоадом, а ридьюсер это обработчик события. Вы ведь не станете спорить что у события может быть сколько угодно разных обработчиков.
— Представьте себе что у вас больше SPA со множеством lazy/ленивых модулей. Любой экшен может быть послан из любого модуля при этом сами экшены очень легковесны. То есть это не так страшно импортировать допустим все экшены или по отдельности в каждый модуль (особенно если это константы, а не бандлы тк tree shaking). Но вы привязали сам обработчик к экшену и в итоге все модули загрузят и всю логику ридьюсеров тоже что не является желательным поведением приложения.
— Я бы еще причин придумал, но написанного считаю уже достаточно.
В традиционной реализации redux есть проблема. И если многие считают проблемой большое количество бойлерплейта и констант, то я считаю проблемой линейную сложность редюсеров от количества экшнов. Если у тебя есть 500 разных типов action, то store.dispatch каждый раз будет делать 500 сравнений ссылок на строку action type
А «Используйте TypeScript» это глупый максимализм. Несмотря на мои определенные к нему симпании, typescript и javascript — разные языки программирования. Вы ведь не приходите в посты про python и не говорите там «Используйте C#»?
то я считаю проблемой линейную сложность редюсеров от количества экшнов.Проблема скорее не в количестве а как часто экшены летают. Оптимизировать резолвинг ридьюсера по типу экшена всегда можно.
А «Используйте TypeScript» это глупый максимализм.Это насущная необходимость при разработке решений сложнее hello world, просто не все это еще осознали.
typescript и javascript — разные языки программирования.Это не так, в рантайме они абсолютно идентичны.
Вы ведь не приходите в посты про python и не говорите там «Используйте C#»?А вот как раз python и C# совсем разные.
Ежеквартальное событие "моему приложению не нужен ридакс, но кто-то его зачем-то выбрал, и теперь я сотворю из ридакса кашу-маляшу".
Решение классное, простое и грамотное. Но в традиционном стиле redux его бы надо было написать как middleware
Что-то в виде:
const functionalReducerMiddleware = store => next => action => {
if (action.func) return action.func(next)
else return next
}
Хотя вообще я бы если интегрировал такое решение, как-то гарантировал бы, что action.type у таких штук будет уникальный и больше нигде не используемый, чтобы ни у кого не пришло в голову обработать такой экшн ещё и в редюсере
Меня тоже всегда убивало это месиво из switch-case
Для меня одна из загадок вселенной состоит в том, на кой чёрт все пишут эти switch-case-ы? Кто мешает выносить каждый case в отдельный метод а оркестрировать их по action.type + hashMap. Собственно когда мы декомпозируем обыкновенный код — мы же не пишем повсюду switch-case-ы. Они ж уродливы.
Народ посмотрел документацию на redux и пошёл копипастить.
Подход интересный, но константы всё-равно нужны. Экшены же как-то надо диспэтчить.
Я правильно понимаю, что unionize
рушит все вкусности на уровне import-export
-а? Т.е. мы работаем не точечно с конкретным actionCreator-ом, когда импортируем что-то, а тащим весь список экшнов и на месте используем нужное? Если да, то я бы не стал такое применять в деле на проектах без TypeScript-а. Мне кажется выгода графа зависимостей превалирует над таким подходом.
Я имел ввиду граф зависимостей а не "вес" кода. В случае TS я могу не переживать, что использую ключ action-а, которого нет. В случае JS такую возможность предоставляет простой import конкретного action-а (что кажется вообще не про unionize). Дальше дело за линтерами.
Ну собственно отдельно лежащие action-type от которых у всех так бомбит — это ведь как раз попытка Дена Абрамса не держать это в голове. Своего рода типизация через import-export-ы )
Я смотрю в экосистеме React в целом любят делать "типы" на JS руками.
Есть 1 интересное решение с константами, которое я задействовал в одном из своих проектов. Опишу только суть:
// action creators
export const aOpen = new ActionBuider('id');
export const aSave = new AsyncActionBuilder(['id', 'data'], asyncHandler);
// reducers
const handlers =
{
[aOpen]: (st, action, rootSt) => { ... },
[aSave]: (st, action, rootSt) => { ... },
};
// somewhere
connect(
mapStateToProps,
{ open: aOpen }
)(SomeComponent)
Может возникнуть вопрос — а где вообще типы? а вот они: aOpen
, aSave
. Самописный babel-plugin просто меняет new ActionBuilder(...).setType('OPEN');
. Если его не подключить, то будет просто уникальный ID аля "id1231". Если подключить — обыкновенные привычные типы. Ну и систему префиксов вдобавок (в примере не стал пудрить мозг с ними)
Рекомендую взглянуть на его развитие https://github.com/alexnm/re-ducks
При вашем подходе экшны более не сериализуемы. Т.е. вам более не смогут прислать пачку экшнов с продавцом, вы у себя сделаете риплей и восстановите стейт.
Второй момент: мне не очень понятно как с таким подходом вы разбиваете стейт на более маленькие кусочки. Учитывая, что вы предлагаете выкинуть combineReducers, получается каждый экшн должен обрабатывать весь стейт целиком?
Легкий аналог redux/react-redux для react/preact от разработчика preact.
Redux — пересмотр логики reducer'a и actions