Не ограничиваясь редукторами комбайнов для рефакторинга редукторов.

При работе с приложением React Redux одна из самых важных вещей, которую вы должны сделать, - это избегать написания больших беспорядочных редукторов и смотреть на их рефакторинг на более мелкие управляемые части. Чаще всего это можно сделать с помощью combReducers. Используя объединенные редукторы, вы в основном разделяете свое состояние на несколько тонких фрагментов, каждый из которых управляется отдельным редуктором меньшего размера. Однако, как также упоминалось в документации redux, это, очевидно, не единственный способ разделить логику редуктора.

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

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

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

const rootReducer = combineReducers({
  batting: battingReducer,
  bowling: bowlingReducer,
  score: scoreReducer
});

При этом каждый редуктор может работать с логическим срезом состояния. Например, scoreReducer работает только с простым фрагментом состояния, например

{      
  team: 'Team1',      
  runs: 0,       
  wickets: 0,      
  balls: 0    
}

Однако общее состояние при объединении редукторов, как указано выше, будет выглядеть следующим образом

{    
  batting: [...],
  bowling: [...]
  score: {...}
}

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

[
  {    
    batting: [...],
    bowling: [...]
    score: {...}
  },
  { ...  
  }
]

Введите редукторы более высокого порядка.

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

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

Вот пример редуктора высшего порядка lastItemInAListReducer

А затем используйте этот редуктор более высокого порядка в корневом редукторе

const rootReducer = combineReducers({
  batting: lastItemInAListReducer(battingReducer),
  bowling: lastItemInAListReducer(bowlingReducer),
  score: lastItemInAListReducer(scoreReducer)
});

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

  • Обратите внимание, что в этом сценарии у нас будет каждый срез как массив, а не все состояние как массив. Это выглядело бы так:
{ 
    batting: [
               [...]
             ],
    bowling: [
               [...]
             ],
    score: [
              {...}
           ]
}

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

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

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

const rootReducer = combineReducers({
  batting: lastItemInAListReducer(battingReducer, NEXT_INNING),
  bowling: lastItemInAListReducer(bowlingReducer, NEXT_INNING),
  score: lastItemInAListReducer(scoreReducer, NEXT_INNING)
});

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