Redux: Проблема с combReducer, когда действие связано с несколькими редукторами

Созданный на 21 авг. 2015  ·  52Комментарии  ·  Источник: reduxjs/redux

Я работаю над чат-приложением, созданным с помощью React.js и Flux. Когда я читал документы Redux, функция combineReducer мне показалась странной. Например:

Есть три магазина: messageStore , unreadStore , threadStore . И есть действие под названием NEW_MESSAGE . Все три магазина будут обновлять новые сообщения. В Redux хранилища представляют собой редукторы в сочетании с combineReducer например:

message = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state
unread = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state
thread = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state

combineReducer {message, unread, thread}

Мне кажется, что в таких случаях редукторы расщепления не облегчают мне жизнь. Однако гигантский магазин, построенный с использованием immutable-js, может быть лучшим решением для этого. Подобно:

initialState = Immutable.fromJS
  message: []
  unread: []
  thread: []

allStore = (store=initialState, action) ->
  switch action.type
    when NEW_MESSAGE
        initialState
        .updateIn ['message'], (messages) -> messages # updated
        .updateIn ['urnead'], (urneads) -> unreads # updated
        .updateIn ['thread'], (threads) -> threads # updated
question

Самый полезный комментарий

для такого действия я должен поддерживать его в нескольких редукторах (или файлах после роста моего приложения). В нашей текущей кодовой базе мы уже видели 5 хранилищ, обрабатывающих одно и то же действие, это немного сложно поддерживать.

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

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

в некоторых случаях один редуктор может зависеть от данных из другого (например, перемещение сообщения из непрочитанного в сообщение или даже продвижение сообщения в качестве новой цепочки из сообщения в цепочку)

это симптом проблемы. Если вам нужно много перемещать объекты внутри вашего состояния, возможно, форму состояния нужно более нормализовать. Вместо { readMessages, unreadMessages } можно использовать { messagesById, threadsById, messageIdsByThreadIds } . Сообщение прочитали? Хорошо, переключи state.messagesById[id].isRead . Хотите читать сообщения в ветке? Возьмите state.threadsById[threadId].messageIds , затем messageIds.map(id => state.messagesById[id]) .

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

Все 52 Комментарий

Не могли бы вы объяснить, почему вы думаете, что combineReducers бесполезны в этом случае? Действительно, для состояния, подобного базе данных, вам часто нужен один редуктор (а затем другие редукторы для состояния пользовательского интерфейса, например, выбранный элемент и т. Д.).

Смотрите также:

https://github.com/rackt/redux/tree/master/examples/real-world/reducers
https://github.com/rackt/redux/blob/master/examples/async/reducers

для вдохновения.

Первый пример, на который вы указали, похож на мой случай, requestType обрабатывается в обоих редукторах.
https://github.com/rackt/redux/blob/master/examples/real-world/reducers/paginate.js#L28

Разделение редукторов упрощает обслуживание, когда два редуктора отделены друг от друга. Но когда они совершают одни и те же действия, это приводит к:

  • для такого действия я должен поддерживать его в нескольких редукторах (или файлах после роста моего приложения). В нашей текущей кодовой базе мы уже видели 5 хранилищ, обрабатывающих одно и то же действие, это немного сложно поддерживать.
  • в некоторых случаях один редуктор может зависеть от данных другого (например, перемещение сообщения из unread в message или даже продвижение сообщения как нового потока из message в thread ). Так что теперь мне может понадобиться получить данные от другого редуктора.

Я не вижу хорошего решения этих двух проблем. На самом деле я не совсем уверен в этих двух проблемах, они похожи на компромисс.

для такого действия я должен поддерживать его в нескольких редукторах (или файлах после роста моего приложения). В нашей текущей кодовой базе мы уже видели 5 хранилищ, обрабатывающих одно и то же действие, это немного сложно поддерживать.

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

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

в некоторых случаях один редуктор может зависеть от данных из другого (например, перемещение сообщения из непрочитанного в сообщение или даже продвижение сообщения в качестве новой цепочки из сообщения в цепочку)

это симптом проблемы. Если вам нужно много перемещать объекты внутри вашего состояния, возможно, форму состояния нужно более нормализовать. Вместо { readMessages, unreadMessages } можно использовать { messagesById, threadsById, messageIdsByThreadIds } . Сообщение прочитали? Хорошо, переключи state.messagesById[id].isRead . Хотите читать сообщения в ветке? Возьмите state.threadsById[threadId].messageIds , затем messageIds.map(id => state.messagesById[id]) .

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

Я хотел бы попробовать это.

Я просто написал демонстрационную страницу, чтобы попробовать redux. Я думаю, что combineReducer принес мне некоторые сложности. И все примеры в репо используют combineReducer , есть ли еще шанс, что я могу использовать единственный неизменяемый объект данных в качестве модели, а не объект JavaScript, содержащий мои данные?

@jiyinyiyong Я действительно не понимаю, о какой сложности вы говорите, но не стесняйтесь использовать combineReducers . Это просто помощник. Обратите внимание на аналогичный помощник, который вместо этого использует Immutable . Или, конечно, напишите свое.

Я написал свой собственный помощник combineReducers() основном для поддержки Immutable.js, но он может потребовать дополнительных редукторов, которые работают как корневые редукторы:
https://github.com/jokeyrhyme/wow-healer-bootcamp/blob/master/utils/combineReducers.js

Первый аргумент соответствует форме вашего состояния и передает подсостояния верхнего уровня соответствующим вспомогательным редукторам, которые вы предоставляете. Это более или менее похоже на официальную версию.

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

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

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

function a (state, action) { /* only sees within a */ return state; }
function b (state, action) { /* only sees within b */ return state; }
function c (state, action) { /* only sees within c */ return state; }

function ab (state, action) { /* partial state containing a and b */ return state; }
function bc (state, action) { /* partial state containing b and c */ return state; }

let rootReducer = combineReducers(
  { a, b, c },
  [ 'a', 'b', ab ],
  [ 'b', 'c', bc ]
);

Стоит ли отправлять PR для частичных редукторов и дополнительных корневых редукторов? Это ужасная идея или это может быть полезно для многих других людей?

Мне больше хочется узнать, как галоген и вяз решают эти проблемы. JavaScript состоит из нескольких программных диаграмм и ведет нас по многим направлениям. Теперь я не понимаю, что является правильным из FRP и однонаправленного потока данных.

Я обнаружил, что это тоже сбивает меня с толку. Просто разместите здесь свой вариант использования / решение.

У меня есть три субредуктора, которые обрабатывают массив из трех разных типов ресурсов (клиенты, проекты, задания). Когда я удаляю клиента ( REMOVE_CLIENT ), все связанные проекты и задания должны быть удалены. Задания связаны с клиентами через проект, поэтому редуктор заданий должен иметь доступ к проектам для выполнения логики объединения. Редуктор заданий должен получить список всех идентификаторов проектов, принадлежащих удаляемому клиенту, а затем удалить все подходящие задания из хранилища.

Я решил эту проблему с помощью следующего промежуточного программного обеспечения:

export default store => next => action => {
  next({ ...action, getState: store.getState });
}

Чтобы каждое действие имело доступ к корневому хранилищу через action.getState() .

@ rhys-vdw, спасибо за это! Действительно полезное промежуточное ПО :)

@ rhys-vdw Спасибо за это - кажется хорошим решением. Как вы обрабатываете крайние случаи / ссылочную целостность, например, когда объект совместно используется двумя (или более) другими объектами или указывает на несуществующую запись во время удаления. Просто хочу услышать ваши мысли по этому поводу.
@gaearon Есть ли в Redux задокументированный способ решения такой проблемы ссылочной целостности нормализованных сущностей?

Нет конкретного способа. Я бы предположил, что это можно автоматизировать, определив схему и создав редукторы на основе этой схемы. Тогда они будут «знать», как собирать «мусор», когда что-то удаляется. Однако все это очень непросто и требует усилий по реализации :-). Лично я не думаю, что удаление происходит достаточно часто, чтобы гарантировать настоящую очистку. Я обычно переворачиваю поле status на модели и стараюсь не отображать удаленные элементы при их выборе. Или, конечно, вы можете обновить при удалении, если это не обычная операция.

@gaearon Приветствую за сверхбыстрый ответ. Да ... Я думаю, что те же усилия прилагаются к работе с ссылочной целостностью при использовании ссылок с документными базами данных, такими как Mongo. Я думаю, что стоит либо иметь чистое состояние без плавающих вокруг мусорных сущностей. Не уверен, что эти призрачные записи могут когда-нибудь сбить вас с толку, особенно когда у вас много операций CRUD в вашем штате.

Однако я также думаю, что было бы здорово, если бы Redux поддерживал как нормализованные, так и встроенные объекты. И можно было выбрать стратегию, которая соответствует цели. Но я предполагаю, что для встроенных сущностей нужно использовать nested reducers что является анти-шаблоном в соответствии с документами Redux.

Мне было трудно получить доступ к остальному состоянию хранилища в редукторе. В некоторых случаях это затрудняет использование вложенных CombineReducers. Было бы хорошо, если бы существовали методы для доступа к остальной части состояния, например .parent() или .root() или аналогичные.

Доступ к состоянию других редукторов в редукторе чаще всего является анти-шаблоном.
Обычно это можно решить:

  • Удаление этой логики из редуктора и перенос ее в селектор;
  • Передача дополнительной информации в действие;
  • Позволить коду представления выполнить два действия.

Благодаря! Я согласен с этим. Я только что обнаружил, что после попытки передать корневое состояние редукторам стало сложнее :-)

Да, проблема с передачей корневого состояния заключается в том, что редукторы становятся связанными с формой состояния друг друга, что усложняет любой рефакторинг или изменение структуры состояний.

Хорошо, я ясно вижу преимущества нормализованного состояния и отношусь к редукционной части моего состояния как к БД.

Я все еще думаю, лучше ли (отметка объектов для удаления) или @ rhys-vdw (разрешение ссылок и удаление связанных ссылок на месте) .

Есть идеи / реальный опыт?

Если redux был больше похож на вяз, а состояние не было нормализовано, тогда имеет смысл разделение редукторов от формы состояния. Однако, поскольку состояние нормализовано, кажется, что редукторам часто требуется доступ к другим частям состояния приложения.

Редукторы уже транзитивно связаны с приложением, поскольку редукторы зависят от действий. Вы не можете тривиально повторно использовать редукторы в другом приложении redux.

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

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

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

@kurtharriger : обратите внимание, что, хотя общий рекомендуемый шаблон состоит в том, чтобы структурировать состояние Redux по доменам и использовать combineReducers для управления каждым доменом отдельно, это _просто_ рекомендация. Вполне возможно написать собственный редуктор верхнего уровня, который управляет вещами по-другому, включая передачу всего состояния определенным функциям подчиненного редуктора. В конечном итоге у вас есть только функция _one _ combineReducers - это просто полезная утилита для общего случая использования.

Ответ на часто задаваемые вопросы Redux по разделению бизнес-логики также содержит хорошее обсуждение этой темы.

Да, другим вариантом было бы просто использовать один редуктор в одной гигантской функции и / или написать свою собственную функцию combReducer вместо использования встроенной в одну.
Было бы полезно иметь встроенный метод combReducers, который добавляет дополнительный аргумент корневого состояния, но PR, которые пытались добавить эту функцию, были закрыты как анти-шаблон. Я просто хотел поспорить с тем, что предлагаемые альтернативы, ИМХО, приводят к гораздо большей общей связи, и, возможно, это не следует рассматривать как антипаттерн. Кроме того, если корневое состояние было добавлено в качестве дополнительного аргумента, это не похоже на то, что вы вынуждены использовать, поэтому связь по-прежнему является опцией.

Мы хотим, чтобы эта связь была явной. Это явно видно, когда вы делаете это вручную, и это буквально N строк кода, где N - сколько у вас редукторов. Только не используйте combReducers и пишите родительский редюсер вручную.

Я новичок в redux / response, но смиренно спросил бы следующее: если составные редукторы, обращающиеся к состоянию друг друга, являются анти-шаблоном, потому что он добавляет связь между разрозненными областями формы состояния, я бы сказал, что эта связь уже имеет место в mapStateToProps ( ) из connect (), поскольку эта функция обеспечивает корневое состояние, а компоненты извлекают отовсюду / откуда угодно, чтобы получить свои реквизиты.

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

По мере того, как я изучаю redux, я борюсь с тем, что, по моему мнению, будет долгосрочной проблемой с моим приложением, а именно с тесной связью формы состояния в приложении - не редукторами, а mapStateToProps - например, боковой панелью комментариев, которая должна знать имя пользователя auth (каждое сокращено, например, редуктором комментариев против редуктора auth). Я экспериментирую с обертыванием всего доступа к состоянию в методах доступа, которые выполняют такую ​​инкапсуляцию, даже в mapStateToProps, но чувствую, что могу лаять не так дерево.

@jwhiting : это одна из нескольких целей использования «функций выбора» для извлечения необходимых данных из состояния, особенно в mapStateToProps . Вы можете скрыть состояние вашей формы, чтобы осталось минимальное количество мест, которым действительно нужно знать, где находится state.some.nested.field . Если вы еще не видели его, посмотрите служебную библиотеку Reselect и раздел Computing Derived Data документации Redux.

Да уж. Вам также не нужно использовать Reselect, если объем данных невелик, но все еще используются (написанные вручную) селекторы. Посмотрите пример shopping-cart для этого подхода.

@markerikson @gaearon, что имеет смысл, и я

Хранение всего кода в одном редукторе - не очень хорошее разделение
проблемы. Если ваш редуктор вызывается корнем, вы можете вызвать его вручную
и явно передать корневое состояние, как я думаю, вы предлагаете, но если ваш
родительская иерархия редукторов содержитcommonReducers, которые вам нужно разорвать
из. Так почему бы не сделать его более сложным, чтобы не нужно было его копировать?
уйти позже или прибегнуть к помощи thunks, как кажется, делает большинство людей?
В субботу, 16 апреля 2016 г., в 20:27 Джош Уайтинг [email protected]
написал:

@markerikson https://github.com/markerikson @gaearon
https://github.com/gaearon, что имеет смысл, и я исследую это,
благодарю вас. Использование селекторов в mapStateToProps для всего за пределами этого
основной задачей компонента (или, возможно, для всего государственного доступа) будет защита
компоненты от изменения внешнего вида государства. Я бы хотел найти способ
чтобы сделать инкапсуляцию состояния в моем проекте более строгой,
так что дисциплина кода не единственная его защита, и приветствуем
предложения есть.

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/reactjs/redux/issues/601#issuecomment -210940305

@kurtharriger : мы все согласны с тем, что хранить все до последнего бита логики редуктора в одной функции - плохая идея, и что работа должна быть каким-то образом делегирована combineReducers как встроенную утилиту для общего случая использования. Если вы не хотите использовать эту утилиту в том виде, в котором она существует в настоящее время, вам не обязательно - вы полностью можете структурировать свои редукторы по-своему, будь то написание всего вручную или написание собственного варианта combineReducers который делает вещи более подходящим для вас способом.

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

А именно, шаблон ajax shouldFetch, который я вижу в разных местах, похоже, тесно связан с формой состояния. Например, здесь . В этом примере, что, если я повторно использую редуктор, но у меня нет объекта сущностей в корне состояния? Тогда это действие потерпит неудачу. Какая здесь рекомендация? Следует ли добавлять действия для конкретного случая? Или действие, которое принимает селектор?

@shadowii Мне кажется, что создание селектора в создателе действий - это нормально. Я бы разместил селекторы с редукторами, чтобы информация о форме состояния была локализована в этих файлах.

В чем проблема с тем, что разные редукторы обрабатывают один и тот же тип действия?
Например, если у меня есть редукторы account и auth , они оба обрабатывают тип действия LOGIN_SUCCESS (с токеном аутентификации и данными учетной записи в полезной нагрузке), каждый одно обновление только части состояния, которым они управляют. Я обнаружил, что использую этот подход довольно много раз, когда действие влияет на разные части состояния.

В чем проблема с тем, что разные редукторы обрабатывают один и тот же тип действия?

Нет проблем, это очень распространенный и полезный шаблон. Фактически, существуют действия _reason_: если действия сопоставлены с редукторами 1: 1, вообще не было бы смысла иметь действия.

@ rhys-vdw Я пробовал использовать размещенное вами промежуточное ПО.

customMiddleare.js

const customMiddleware =  store => next => action => {
  next({ ...action, getState: store.getState });
};

и в моем магазине создания у меня есть

import customMiddleware from './customMiddleware';

var store = createStore(
        rootReducer,
        initialState,
        applyMiddleware(
            reduxPromise,
            thunkMiddleware,
            loggerMiddleware,
            customMiddleware
        )
    );
return store;

Я получаю следующую ошибку:

applyMiddleware.js? ee15: 49 Uncaught TypeError: промежуточное ПО не является функцией (…)
(анонимная функция) @ applyMiddleware.js? ee15: 49
(анонимная функция) @ applyMiddleware.js? ee15: 48
createStore @ createStore.js? fe4c: 65
configureStore @ configureStore.js? ffde: 45
(анонимная функция) @ index.jsx? fdd7: 25
(анонимная функция) @ bundle.js: 1639
webpack_require @ bundle.js: 556
fn @ bundle.js: 87 (анонимная функция) @ bundle.js: 588
webpack_require @ bundle.js: 556
(анонимная функция) @ bundle.js: 579
(анонимная функция) @ bundle.js: 582

@ mars76 : Скорее всего, вы что-то неправильно импортируете. Если это действительно все содержимое customMiddleware.js , значит, вы не экспортируете ничего _. Как правило, вы передаете четыре промежуточных ПО в applyMiddleware , и предположительно одно из них не является действительной ссылкой. Сделайте шаг назад, удалите все из них, добавьте по одному, посмотрите, какой из них недействителен, и выясните, почему. Проверьте операторы импорта и экспорта, дважды проверьте, как вы должны инициализировать вещи и т. Д.

Привет,

Спасибо @markerikson

Я преобразовал синтаксис, как показано ниже, и теперь все в порядке:

customMiddleware.js

export function customMiddleware(store) { return function (next) { return function (action) { next(Object.assign({}, action, { getState: store.getState })); }; }; } console.log(customMiddleware);

Как бы то ни было, я пытаюсь получить доступ к магазину в самом Action Creator (а не в Reducer). Мне нужно сделать асинхронный вызов и передать разные части состояния хранилища, управляемые различными редукторами.

Вот что я делаю. Не уверен, что это правильный подход.

Я отправляю действие с данными и внутри редуктора у меня теперь есть доступ ко всему состоянию, и я извлекаю необходимые части, которые мне нужны, и создаю объект и отправляю другое действие с этими данными, которое будет доступно в моем Action Creator, где я я делаю вызов Asyn, используя эти данные.

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

Ценю любые комментарии.

благодаря

@ mars76 : опять же, вы никогда не должны

Для доступа к хранилищу внутри ваших создателей действий вы можете использовать промежуточное программное обеспечение redux-thunk . Вы также можете прочитать FAQ по обмену данными между редукторами , а также суть, которую я написал с некоторыми примерами распространенного использования преобразователей .

Большое спасибо @markerikson.

Мое плохое: я не знал о getState () в redux-thunk. Это значительно упрощает мою работу и оставляет мои редукторы чистыми.

@gaearon, когда вы упомянули, что создатель действия импортирует селектор , вы думали, что все состояние будет передано создателю действия для передачи селектору, или я что-то упустил?

@tacomanator : К вашему сведению, в наши дни Дэн обычно не отвечает на случайные пинги в проблемах Redux. Слишком много других приоритетов.

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

import {someSelector} from "./selectors";

function someThunk(someParameter) {
    return (dispatch, getState) => {
        const specificData = someSelector(getState(), someParameter);

        dispatch({type : "SOME_ACTION", payload : specificData});    
    }
}

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

Быстрый прием, который я использовал для объединения редукторов в один, был

const reducers = [ reducerA, reducerB, ..., reducerZ ];

let reducer = ( state = initialState, action ) => {
    return reducers.reduce( ( state, reducerFn ) => (
        reducerFn( state, action )
    ), state );
};

где от reducerA до reducerZ - отдельные функции редуктора.

@brianpkelley : да, это в основном https://github.com/acdlite/reduce-reducers .

@markerikson А, хорошо, впервые использовал

Хех, конечно.

К вашему сведению, вы можете прочитать FAQ: «Обязательно ли мне использовать combineReducers и Структурирование редукторов разделов в документации. Кроме того, в моей серии руководств «Практический Redux» есть пара сообщений, демонстрирующих некоторые продвинутые приемы редуктора (см. Часть 7 и Часть 8).

Мои два цента при использовании redux-логики - это увеличение действия от корневого состояния в функции transform .

Пример:

const formInitLogic = createLogic({
  type: 'FORM_INIT',
  transform({ getState, action }, next) {
    const state = getState();
    next({
      ...action,
      payload: {
        ...action.payload,
        property1: state.branch1.someProperty > 5,
        property2: state.branch1.anotherProperty + state.branch2.yetAnotherProperty,
      },
    });
  },
});

я думаю, это больше о структуре кода:
"не обращаться напрямую к подсостояниям"
после использования redux во многих проектах я обнаружил, что лучшая структура кода - иметь дело с каждым подсостоянием как с полностью отдельным компонентом, каждый из этих компонентов имеет свою собственную папку и логику, для реализации этой идеи я использую следующую структуру файлов:

  • корень редукции

    • reducers.js экспорт объединяет редукторы из каждого штата

    • middleware.js export applyMiddleware для всего промежуточного ПО

    • configureStore.js createStore с использованием (reducers.js, middleware.js)

    • actions.js экспортирует действия, которые отправляют действия из папок состояний <== ЭТО

    • состояние1

    • initial.js этот файл содержит начальное состояние (я использую неизменяемый для создания записи)

    • action-types.js этот файл содержит типы действий (я использую dacho в качестве ключевого зеркала)

    • actions.js этот файл содержит действия состояния

    • reducer.js этот файл содержит редуктор состояния

    • состояние2

    • initial.js

    • действие-типы.js

      ....

Почему ?
1- в каждом приложении есть 2 типа действий:

  • действия состояния (redux / state1 / actions.js) ответственность этих действий только за изменение состояния, вы не должны запускать эти действия напрямую, всегда используйте действия приложения

    • действия приложения (redux / actions.js) эти действия отвечают за выполнение некоторой логики приложения и могут запускать действия состояния

2- дополнительно используя эту структуру, вы можете создать новое состояние, просто скопировав и вставив папку и переименовав ее;)

А как насчет того, чтобы двум редукторам нужно было изменить одно и то же свойство?

Допустим, у меня есть редуктор, который я хотел бы разделить на разные файлы, и что есть свойство под названием «ошибка», которое я меняю, когда HTTP-запросы по той или иной причине не работают.

Теперь в какой-то момент редуктор стал слишком большим, поэтому я решил разделить его на несколько редукторов, но всем им нужно изменить это свойство «error».

И что?

@ Wassap124 Два редуктора не могут управлять одним и тем же состоянием. Вы имеете в виду, что два действия должны изменить одно и то же свойство?

Без дополнительных подробностей вы можете предпочесть использовать преобразователь для отправки действия «установить ошибку».

@ rhys-vdw Я немного застрял, пытаясь разделить довольно длинный редуктор.
Что касается вашего предложения, разве это не противоречит правилу отсутствия побочных эффектов?

@ Wassap124 , @ rhys-vdw: чтобы уточнить, два редуктора не могут управлять одним и тем же состоянием _ если вы используете только combineReducers _. Однако вы, безусловно, можете написать другую логику редуктора в соответствии с вашим собственным вариантом использования - см. Https://redux.js.org/recipes/structuringreducers/beyondcombinereducers .

Если вы быстро изучите источники ...

combineReducers возвращает подпись функции, например

return function combination(state = {}, action) {

см. https://github.com/reduxjs/redux/blob/master/src/combineReducers.js#L145

после создания замыкания для ссылки на фактический объект редукторов.

Следовательно, вы можете добавить что-то простое, чтобы глобально обрабатывать действия в случае необходимости гидратировать и перезагружать состояние из хранилища.

const globalReducerHandler = () => {
  return (state = {}, action) => {
    if (action.type === 'DO_GLOBAL_THING') {
      return globallyModifyState(state)
    }
    return reducers(state, action)
  }
}

Это может быть или не быть антипаттерном, но прагматизм всегда был важнее.

Вместо вызова редуктора из другого редуктора (который является антипаттерном, потому что globalReducerHandler не имеет ничего общего с вызываемым редуктором) используйте reduceReducers , что по сути является compose для редукторов (ну, это буквально композиция в монаде (Action ->) ).

Была ли эта страница полезной?
0 / 5 - 0 рейтинги