Имеют ли события и действия отношения 1:1 в Redux?
У событий (событий DOM или системных событий) есть отношения 1:1 с действиями? т.е. если одно событие щелчка запускает только одно действие?
Например, скажем, у нас есть страница, которая отображает таблицу из 10 строк и 2 столбца. Каждая строка имеет поле "Продукт" и поле "Сумма". Поле "Сумма" имеет вход диапазона с диапазоном [0, 10]. Пользователь может установить количество каждого продукта отдельно.
Пользователю также предоставляется 2 варианта с помощью 2 кнопок.
- Нажатие второй кнопки отключит все, кроме первого продукта в таблице (эффективно установив их значение в 0, и пользователь больше не сможет взаимодействовать с ними, чтобы установить их количество). Позвольте называть это
Option B
- При нажатии первой кнопки все Продукты после первого (по умолчанию устанавливают их количество на 1 для каждого из них), и пользователь может снова взаимодействовать с ними, чтобы установить их суммы индивидуально. Позвольте называть это
Option A
.
Option A selected:
| PRODUCT | AMOUNT |
|------------------|-----------|
| Product A | - 4 + |
| Product B | - 0 + |
| Product C | - 4 + |
````````````````````````````````
_________
| Option A| OPTION B
`````````
Option B selected:
| PRODUCT | AMOUNT |
|------------------|-----------|
| Product A | - 4 + |
| Product B | Disabled | (Amount == 0)
| Product C | Disabled | (Amount == 0)
````````````````````````````````
_________
OPTION A | OPTION B|
`````````
Option A selected again:
| PRODUCT | AMOUNT |
|------------------|-----------|
| Product A | - 4 + |
| Product B | - 1 + |
| Product C | - 1 + |
````````````````````````````````
_________
| Option A| OPTION B
`````````
Состояние этого "приложения" описывается этим простым объектом
state = {
option : <String>,
products : [
{
name : <String>,
amount : <Integer>
}, ...
]
}
У нас также есть эти 4 простых создателя действия:
function setOption(option) {
return { type : 'SET_OPTION', option : option};
}
function incAmount(productName) {
return {
type : 'INCREMENT_AMOUNT',
product : productName
}
}
function decAmount(productName) {
return {
type : 'DECREMENT_AMOUNT',
product : productName
}
}
function setAmount(productName, amount) {
return {
type : 'SET_AMOUNT',
payload : { product : productName, amount : amount }
}
}
Для простоты мы имеем только один редуктор.
В этом примере выбор Option B
должен иметь следующие эффекты для состояния:
- Измените
option
на B
- Задайте количество каждого
product
после первого значения 0
Выбор Option A
должен иметь следующие эффекты для состояния, соответственно:
- Измените
option
на A
- Задайте количество всех
product
после первого значения 1
Приращение количества продукта A должно иметь следующие эффекты для состояния:
- Увеличьте количество продукта A на 1
Каким будет правильный способ реализовать эти изменения?
a) Попросите обработчик onClick
кнопок option
выполнить следующие действия:
- Пожар a
store.dispatch(setOption(option))
- Для каждого продукта после первого пожара a
store.dispatch(setAmount(productName, amount))
(amount
= 1 для опции A, 0 для опции B)
b). Обработчик onClick
кнопок option
выполняет следующие действия:
-
Пожар a store.dispatch(setOption(option))
И замените редуктор на option
, а также amount
каждого продукта после первого на указанное количество (amount
= 1 для опции A, 0 для опции B)
Если мы идем с a), каждый случай в выражении switch (action) {}
редуктора имеет дело только с одним аспектом состояния, но мы должны запускать более одного действия из одного click
событие
Если мы идем с b), мы запускаем только одно действие из события click
, но случай для SET_OPTION
в редукторе не только изменяет option
, но также и amount
продуктов.
Ответы
Ответ 1
Нет общего ответа на этот вопрос, поэтому мы должны оценивать каждый случай.
При использовании Redux вы должны стремиться поддерживать баланс между сохранением простых редукторов и значимым ведением журнала действий. Лучше всего, когда вы можете прочитать журнал действий, и имеет смысл, почему все произошло. Это аспект "предсказуемости", который предлагает Redux.
Когда вы отправляете одно действие и изменяете его в разных частях, легко понять, почему они меняются позже. Если вы отлаживаете проблему, вы не будете поражены количеством действий, и каждая мутация может быть прослежена до того, что сделал пользователь.
По контрасту, когда вы отправляете несколько действий в ответ на одно взаимодействие с пользователем, сложнее сказать, почему они были отправлены. Они загромождают журнал действий, и если есть ошибка в том, как они были отправлены, журнал не обнаруживает основополагающих причин.
Хорошим правилом является то, что вы никогда не хотите dispatch
в цикле. Это крайне неэффективно и, как отмечено выше, скрывает истинную природу того, почему произошло изменение. В вашем конкретном примере я бы рекомендовал запустить одно действие.
Однако это не означает, что стрельба одним действием всегда является способом выхода. Как и все, это компромисс. Существуют допустимые случаи, когда более удобно запускать несколько действий в ответ на одно взаимодействие пользователя.
Например, если ваше приложение позволяет пользователям тегировать продукты, удобнее будет отделять действия CREATE_TAG
и ADD_TAG_TO_PRODUCT
, потому что, хотя в этом случае они происходят одновременно, они могут также произойти отдельно, и это может проще писать редукторы, которые обрабатывают их как разные действия. Пока вы не злоупотребляете этим шаблоном и не делаете что-то подобное в цикле, вы должны быть в порядке.
Храните журнал действий как можно ближе к истории взаимодействия с пользователем. Однако, если это затрудняет реализацию редукторов, рассмотрим разделение некоторых действий на несколько, если обновление пользовательского интерфейса можно рассматривать как две отдельные операции, которые просто происходят вместе. Не попадайте ни в одну из крайностей. Предпочитайте чистоту редуктора до идеального логарифма, но также предпочитайте не отправлять в петлю до ясности редуктора.
Ответ 2
Чтобы добавить к Dan отличный ответ, когда вы идете по b), вы все равно можете обрабатывать отдельные части состояния, как вы сказали в a), путем расщепление корневого редуктора на более мелкие, например Redux docs показать. Вы должны разделить обработку состояния, составив редукторы, а не произвольно отправляя другие действия. Как сказал Дэн, он помогает в действиях, объясняющих, почему.