Будем о говорить о нескольких (пяти, если быть конкретным) способах, трюках, кровавых жертвах богу энтерпрайза, которые вроде как должны помочь нам писать более лаконичный и выразительный код в наших Redux (и NGRX!) приложениях. Способы выстраданы потом и кофе. Просьба сильно пинать и критиковать. Будем учиться кодить лучше вместе. Честно говоря, мне сначала просто хотелось рассказать миру о своей новой микро-библиотеке (35 строк кода!) flux-action-class, но, глядя на все возрастающее количество возгласов о том, что Хабр скоро станет Твиттером, да и по-большей части с ними соглашаясь, решил попробовать сделать несколько более емкое чтиво. Итак, встречаем 5 способов прокачать ваше Redux приложение! Рассмотрим типичный пример того, как можно послать AJAX запрос в Redux. Давайте представим, что нам крайне необходим список котиков с сервера. Если вам не совсем понятно, зачем тут нужны фабрики для селекторов, то можете почитать об этом здесь Я сознательно не рассматриваю здесь сайд эффекты. Это тема для отдельной статьи полной подросткового гнева и критики в адрес существующей экосистемы :D У этого кода можно найти несколько слабых мест: Ну, не совсем. Просто мы заставим JS создавать их за нас. Задумаемся на секунду о том, зачем нам вообще нужны типы у экшнов. Что ж, очевидно, чтобы запускать нужную ветку логики в нашем редьюсере и соответствующим образом менять состояние приложения. Настоящий вопрос в том, обязательно ли тип должен быть строкой? А что если бы мы использовали классы и делали switch по типу? Все вроде здорово, но есть одна проблема: мы лишились сериализации наших экшнов. Это более не простые объекты, которые мы можем конвертировать в строку и обратно. Теперь мы полагаемся на то, что у каждого экшна есть свой уникальный прототип, что, собственно, и позволяет такой конструкции, как switch по action.constructor, работать. Знаете, а мне очень нравится идея сериализации моих экшнов в строку и отправка их вместе с баг репортом, и я от нее отказываться не готов. Итак, у каждого экшна должно быть поле type (здесь можно посмотреть, что еще должно быть у каждого уважающего себя экшна). К счастью, у каждого класса есть имя, которое вроде как строка. Давайте добавим каждому классу геттер type, который будет возвращать имя этого класса. Это даже работает, но хотелось бы еще каждому типу прилепить префикс, как предлагает мистер Эрик в ducks-modular-redux (рекомендую глянуть на форк re-ducks, который еще круче, как по мне). Для того, чтобы добавить префикс, нам придется перестать использовать имя класса напрямую, а добавить еще один геттер. Теперь уже статический. Давайте все это дело немного причешем. Сократим до минимума copy-paste и добавим еще одно условие: если экшн представляет ошибку, то его payload должен быть типа Error. На данном этапе этот код отлично работает с NGRX, но Redux такое прожевать не способен. Он ругается на то, что экшны должны быть простыми объектами. К счастью, JS позволяет нам возвращать почти что угодно из конструтора, а нам на самом деле не очень-то и нужна цепочка прототипов после создания экшна. На основе вышеизложенных размышлений была написана микро-библиотека flux-action-class. Там есть тесты, 100% покрытие кода тестами и почти тот же класс ActionStandard приправленный дженериками для нужд TypeScript. Работает как с TypeScript, так и с JavaScript. Идея проста до безобразия: использовать combineReducers не только для редьюсеров верхнего уровня, но и для дальнейшего разбиения логики и создания отдельного редьюсера для loading. И снова предельно простая идея: вместо switch-case использовать объект, из которого выбирать нужное поле по ключу. Доступ к полю объекта по ключу — O(1), да и выглядит порядком чище по моему скромному мнению. Давайте немного отрефакторим reducerLoading. Теперь, зная про мапы (объекты) для редьюсеров, мы можем вернуть эту самую мапу из reducerLoading, вместо того, чтобы вернуть целый редьюсер. Потенциально, это открывает неограниченный простор для расширения функционала. Официальная документация на Redux тоже рассказывает про этот подход, однако, по какой-то неведомой причине я продолжаю видеть множество проектов, использующих switch-case. На основе кода из официальной документации мистер Моше запилил для нас библиотеку для createReducer. Нам совершенно не обязательно держать ошибку для каждой сущности отдельно. В большинстве случаев мы просто хотим показать диаложку. Одну и ту же диаложку с динамическим текстом для всех сущностей. Создадим глобальный обработчик ошибок. В самом простом случае он может выглядеть так: Затем в нашем сайд эффекте будем отправлять экшн ErrorInit в блоке catch. Это может выглядеть как-то так при использовании redux-thunk: Теперь мы можем избавиться от поля error в нашем сторе для котиков и использовать CatsGetError лишь затем, чтобы переключать флаг loading. Посмотрим на нагромождение фабрик для селекторов еще раз. Я выкинул makeSelectorCatsError, потому что он больше не нужен, как мы выяснили в предыдущей главе. А зачем нам тут мемоизованные селекторы? Что конкретно мы пытаемся мемоизовать? Доступ к полю объекта по ключу, что здесь и происходит, — O(1). Мы можем использовать обычные немемоизованные функции. Используйте мемоизацию только тогда, когда вы хотите изменить данные из стора перед тем как отдать их компоненте. Мемоизация имеет смысл в случае вычисления результата на лету. Для примера ниже давайте представим, что каждый котик — это объект с полем name, и мы хотим получить строку, содержащую имена всех котиков. Посмотрим еще раз с чего мы начали: И к чему пришли: Надеюсь, вы не потратили время зря, и статья была вам хоть чуточку полезна. Как я говорил в самом начале, просьба сильно пинать и критиковать. Будем учиться кодить лучше вместе. Software Engineer