Pull to refresh

Comments 17

Как правильно произвести декомпозицию редьюсеров, то есть создать несколько независимых редьюсеров, которые преобразуют независимые состояния, и затем объединить их в один редьюсер? Если создать несколько редьюсеров через redux-symbiote и объединить их через combineReducers, то возникнет риск коллизии названий типов действий (потому что они не записаны в коде явно).

Спасибо за вопрос. `createSymbiote` имеет третий параметр namespace/options, с помощью которого можно задать префикс для всех экшен-типов сразу.
github.com/sergeysova/redux-symbiote#options

const { actions, reducers } = createSymbiote(
  initialState, symbiotes, 'prefix/namespace'
)
// or
const { actions, reducers } = createSymbiote(
  initialState,
  symbiotes,
  { namespace: 'prefix/namespace' },
)


Некоторые примеры использования symbiote можно посмотреть здесь: github.com/howtocards/frontend/tree/dev/src/features/cards/symbiotes
Мы как-то сделали себе похожий велик на одном проекте на TypeScript. Получилось ну очень похоже:

const initialState: CounterState = {
    current: 0
}

const next = ({ }) => (s: MyState) => ({ ...s, current: s.current + 1 });
const set = (a: { value: number}) => (s: MyState) => ({ ...s, current: s.current + a.value });

const { actionCreators, bindActions, reducer } = buildReducer(
    initialState,
    {
        next,
        set,
    },
    { prefix: "COUNTER" }
);


В отличии от велика из статьи, тут все строго типизировано. Например, типы actionCreator-ов и их параметров выводятся (мы ради этого карировали функции, action => state => state вместо (action, state) => state).

Плюс имена экшнов автоматом строятся, и никаких функций в action-ы не упаковывается. Т.е. в нашем случае actionCreators.set({ value: 10 }) => { type: 'COUNTER_SET', value: 10 }.

Вот тут исходники самой утилитки: gist.github.com/jakobz/ae3e5567e20fff3d66d9e8852a9a655a
Я позволю себе скопипастить сюда свое сообщение из другой темы:
Итак имеем:
— Глобальный стор (обычно все глобальное — плохо, а тут вдруг — хорошо).
— Кучу экшенов где action.type уникален в пределах приложения.
— Кучу action creator-ов c названием, в большинстве случаев, таким же как action.type, записанным в другом регистре.
— reducer-ы — по сути обычный switch case.

Теоретически, для уменьшения бойлерплейта вы даже использовали какую-нибудь свистоперделку.

Вопрос:
Где в этом хозяйстве архитектура, абстракции и изоляции и чем оно координально лучше, чем обычные методы хранилища? ЗЫ: Методы хотя бы можно комбинировать в отличии от словоблудия внутри switch case.
Я так и не нашел ответа на вопрос зачем нужны эти псевдоабстракции.
Спасибо за вопросы.

Сразу оговорюсь, что статья не об архитектуре в целом, а только об инструменте, который позволяет уменьшить количество кода, повысить читабельность и структурировать часть архитектуры redux.

Не считаю, что глобальный стор или несколько распределенных, а также вызов напрямую методов или через дополнительную инфраструктуру это плохо или хорошо. Каждый использует при разработке, что ему удобнее и подходит для реализации.

Архитектура redux неплохо описана в документации и легко бьется на слои:
— стор отдельно, что и как сделать со стором отдельно,
— представление: глупые компоненты, контейнеры, селекторы данных,
— для перехвата действий и последующего комбинирования действий над стором (и не только), например, redux-saga.

Все это можно легко передавать между проектами и тестировать.

Идеального решения нет, как и ответа на все вопросы.
Вы не подумайте что это была претензия к вам и вашей статье. Сам использовал похожие решения и велосипедил ради интереса. Они вполне жизнеспособны, особенно если учесть, что react/redux — достаточно популярные библиотеки.
Как было нечитебильное г, так и осталось
Не понятно как на такие действия подписываться в том же redux-saga? У них можно имя получить, через какой-то условный name?
yield takeEvery(actions.open.success.name, loadAdditional)
будет работать?
yield takeEvery(actions.open.success.toString(), loadAdditional)
Тогда ок. А с типизацией в TypeScript или Flow как обстоят дела, не в курсе?
Как вижу подобный код и реализацию — честно, плакать хочется. Ну вот реально вьехать с пол пинка вряд ли получится. Далее все что только можно суем в глобальный стор. Туда жа запихавают логику приложения. Вот попробуйте потом такое приложение оптимизировать. Разбить на слои, где с каждым слоем работала бы команда.

Почему не вынести бизнес логику получения и обработки данных в отдельные слои? Зачем все держать в сторе, если данные нужны только в одном месте? Как сделать что-то не завязываясь на redux?

В большинстве туториалов пишут о том, что это серебряная пуля, вот берем и радость. Как сказал один девелопер у меня в команде — «чувак, ты не понимаешь?! Это же React-way! Надо только так писать, так в мануалах пишут». Над… В общем, на практике, это просто добавляет проблем, нежели профита.

Что делаю у себя в проектах, это в первую очередь разделяю приложение на слои: бизнес логика, API, компоненты, Store.

— API это набор классов, где реализована коммуникация с сервером
— бизнес логика, слой где обрабатываются и подготавливаются данные
— компоненты, по содержат логику только для отрисовки интерфейса, максимально стараемся делать их независимыми, маленькими и тупыми.
— Store. В сторе держим только те данные, которые нужны нескольким компонентам в одно и тоже время на странице. К примеру, профиль пользователя. Имя пользователя покажем в хедере сайта, в навигационном меню и на странице профиля пользователя. Изменили имя, поменялось в трех точках.

Как видно из описания, нет проблем разделить работы между людьми. Не жестких привязок. В любой момент можно заменить слой на другую технологию, не переписывая все остальное.
Как потом эти бизнес слои связывать вместе используя Flux?
Как это дебажить без вменяемых dev-tools?

redux-symbiote был призван решить только одну проблему: убрать бойлерплейт вокруг экшенов и редюссеров, ни больше, ни меньше.
symbiote — чисто апдейтеры стора, thunk/execute — бизнес-логика, components — отображение данных. Всё тоже самое разделение на слои, только более простое.
Разделять приложение между командами нужно полноценно разделяя приложение на micro-frontends, а не работать всем вместе в огромном монолите.

А вот по поводу качественного разделения: я постепенно перехожу на effector. Где есть и полноценный дебаг и статическое вычисление зависимых сторов, и красивое API, и отстутствие проблемы ромбовидных зависимостей.

Мб потом статью о нём напишу.
А вот по поводу качественного разделения: я постепенно перехожу на effector. Где есть и полноценный дебаг и статическое вычисление зависимых сторов, и красивое API, и отстутствие проблемы ромбовидных зависимостей.

Мб потом статью о нём напишу.


Было бы неплохо
Разбить на слои, где с каждым слоем работала бы команда.

Вообще нет проблем разбить на слои.

В данном случае redux-symbiote позволяет уменьшить количество кода, повысить читабельность и структурировать часть архитектуры redux.

У нас все проекты послойные:
— стор (redux)
— что и как сделать со стором redux-symbiote,
— представление: глупые компоненты, контейнеры (модули), селекторы данных (как раз группируют данные для контейнеров из разных частей стора),
— бизнес логика — redux-saga,
— апи и сервисы с возможностью моков.

Уже проектов 10+ с использованием redux разработано — проблем не возникает.

Абсолютно не понимаю зачем этот холивар глобальный стор vs распределенный. Статья не об этом. Каждый выбирает инструмент по необходимости.

Я не понимаю, почему люди не переходят на elm, если вам так важно убрать бойлерплейт код, что вы готовы создовать либы вокруг редакса чтоб от него избавится. Ведь редакс пришел именно из ельма, и там те же экшены и редюсер выглядит наиболее выгодно и локанично. Вот тот же пример на elm:


type alias Model =
  { loading : Bool
  , error : Just String
  , popupsOpened : List String
  }

init : Model
init =
  { loading = True
  , error = Nothing
  , popupsOpened = ["popup1", "popup2", "popup3"]
  }

type FetchingStatus
  = Start String
  | Fail String
  | Success String

type Msg
  = Open FetchingStatus
  | Close FetchingStatus

update : Msg -> Model -> Model
update msg model =
  case msg of
    Open status ->
      case status of
        Start ->
          { model | loading = True, error = Nothing }

        Fail error ->
          { model | error = Just error }

        Success popupName ->
          { model | popupsOpened = model.popupsOpened :: popupName }

    Close status ->
      case status of
        Start ->
          { model | loading = True, error = Nothing }

        Fail error ->
          { model | error = Just error }

        Success popupName ->
          { model | popupsOpened = List.filter (\x -> x not popupName) model.popupsOpened }
Sign up to leave a comment.

Articles