事件和动作在 Redux 中是否有 1:1 关系?
Do events and actions have a 1:1 relationship in Redux?
事件(DOM事件或系统事件)与动作有1:1关系吗?即一次单击事件应该只触发一个动作吗?
例如,假设我们有一个显示 table 10 行 2 列的页面。每行都有一个 Product 字段和一个 Amount 字段。 Amount 字段有一个范围为 [0, 10] 的范围输入。用户可以单独设置每个产品的金额。
用户还可以通过使用 2 个按钮获得 2 个选项。
- 按下第二个按钮将禁用 table 中除第一个产品以外的所有产品(实际上将其金额设置为 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
`````````
这个'app'的状态由这个简单的对象描述
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 }
}
}
为了简单起见,我们只有一个reducer。
在此示例中,选择 Option B
会对状态产生以下影响:
- 将
option
更改为B
- 设置第一个到
0
之后每个product
的数量
选择Option A
应该对状态有以下影响,分别是:
- 将
option
更改为A
- 将第一个之后的每个
product
的数量设置为 1
增加产品 A 的数量应该对状态产生以下影响:
- 将产品 A 的数量增加 1
实施这些更改的正确方法是什么?
a) 让 option
按钮的 onClick
处理程序执行以下操作:
- 发射
store.dispatch(setOption(option))
- 对于第一个触发后的每个产品
store.dispatch(setAmount(productName, amount))
(amount
= 选项 A 为 1,选项 B 为 0)
b) 让 option
按钮的 onClick
处理程序执行以下操作:
发射一个store.dispatch(setOption(option))
并让 reducer 将第一个产品之后的每个产品的 option
和 amount
更改为指定数量(amount
= 1 对于选项 A,0 对于选项 B)
如果我们使用 a) 减速器的 switch (action) {}
语句中的每个案例只处理状态的一个方面,但我们必须触发多个来自一个 click
事件的一个动作
如果我们使用 b) 我们只会从 click
事件中触发一个动作,但是在 reducer 中 SET_OPTION
的情况不仅改变了option
还有 amount
的产品。
这个问题没有统一的答案,所以我们必须根据具体情况进行评估。
使用 Redux 时,您应该努力在保持 reducer 简单和保持操作日志有意义之间保持平衡。最好是您可以阅读操作日志并且了解 为什么 事情发生了。这就是Redux带来的“可预测性”方面。
当您调度单个操作时,状态的不同部分响应发生变化,很容易告诉您为什么它们稍后会发生变化。如果您调试一个问题,您不会被大量的操作所淹没,并且每个突变都可以追溯到用户所做的事情。
相比之下,当您分派多个操作以响应单个用户交互时,很难说出为什么分派它们。它们使操作日志混乱,如果它们的调度方式有误,日志将无法揭示根本原因。
一个好的经验法则是你永远不想 dispatch
在一个循环中。这是非常低效的,并且如上所述,掩盖了 为什么 发生变化的真实性质。在您的特定示例中,我建议触发一个动作。
然而,这并不意味着触发单个动作是始终的方式。像所有事情一样,这是一种权衡。在某些情况下,触发多个操作以响应单个用户交互会更方便。
例如,如果您的应用允许用户标记产品,那么将 CREATE_TAG
和 ADD_TAG_TO_PRODUCT
操作分开会更方便,因为在这种情况下它们同时发生,它们也可能单独发生,并且编写将它们作为不同操作处理的 reducer 会更容易。只要你不滥用这个模式并且不在循环中做这样的事情,你应该没问题。
使操作日志尽可能接近用户交互的历史记录。但是,如果它使 reducer 难以实现,请考虑将一些操作分成几个,如果 UI 更新可以被认为是恰好在一起的两个独立操作。不要陷入任何一个极端。更喜欢 reducer 的清晰度而不是完美的日志,但也更喜欢不在循环中调度以简化 reducer 的清晰度。
为了补充 Dan 的出色回答,当您采用 b) 方式时,您仍然可以像 a 中所说的那样处理状态的不同部分) 通过将根减速器拆分成更小的减速器,比如 Redux docs show。您应该通过组合 reducer 来拆分状态处理,而不是通过任意分派其他操作。正如 Dan 所说,它有助于表达 为什么 .
事件(DOM事件或系统事件)与动作有1:1关系吗?即一次单击事件应该只触发一个动作吗?
例如,假设我们有一个显示 table 10 行 2 列的页面。每行都有一个 Product 字段和一个 Amount 字段。 Amount 字段有一个范围为 [0, 10] 的范围输入。用户可以单独设置每个产品的金额。
用户还可以通过使用 2 个按钮获得 2 个选项。
- 按下第二个按钮将禁用 table 中除第一个产品以外的所有产品(实际上将其金额设置为 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 `````````
这个'app'的状态由这个简单的对象描述
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 }
}
}
为了简单起见,我们只有一个reducer。
在此示例中,选择 Option B
会对状态产生以下影响:
- 将
option
更改为B
- 设置第一个到
0
之后每个
product
的数量
选择Option A
应该对状态有以下影响,分别是:
- 将
option
更改为A
- 将第一个之后的每个
product
的数量设置为1
增加产品 A 的数量应该对状态产生以下影响:
- 将产品 A 的数量增加 1
实施这些更改的正确方法是什么?
a) 让 option
按钮的 onClick
处理程序执行以下操作:
- 发射
store.dispatch(setOption(option))
- 对于第一个触发后的每个产品
store.dispatch(setAmount(productName, amount))
(amount
= 选项 A 为 1,选项 B 为 0)
b) 让 option
按钮的 onClick
处理程序执行以下操作:
发射一个
store.dispatch(setOption(option))
并让 reducer 将第一个产品之后的每个产品的
option
和amount
更改为指定数量(amount
= 1 对于选项 A,0 对于选项 B)
如果我们使用 a) 减速器的 switch (action) {}
语句中的每个案例只处理状态的一个方面,但我们必须触发多个来自一个 click
事件的一个动作
如果我们使用 b) 我们只会从 click
事件中触发一个动作,但是在 reducer 中 SET_OPTION
的情况不仅改变了option
还有 amount
的产品。
这个问题没有统一的答案,所以我们必须根据具体情况进行评估。
使用 Redux 时,您应该努力在保持 reducer 简单和保持操作日志有意义之间保持平衡。最好是您可以阅读操作日志并且了解 为什么 事情发生了。这就是Redux带来的“可预测性”方面。
当您调度单个操作时,状态的不同部分响应发生变化,很容易告诉您为什么它们稍后会发生变化。如果您调试一个问题,您不会被大量的操作所淹没,并且每个突变都可以追溯到用户所做的事情。
相比之下,当您分派多个操作以响应单个用户交互时,很难说出为什么分派它们。它们使操作日志混乱,如果它们的调度方式有误,日志将无法揭示根本原因。
一个好的经验法则是你永远不想 dispatch
在一个循环中。这是非常低效的,并且如上所述,掩盖了 为什么 发生变化的真实性质。在您的特定示例中,我建议触发一个动作。
然而,这并不意味着触发单个动作是始终的方式。像所有事情一样,这是一种权衡。在某些情况下,触发多个操作以响应单个用户交互会更方便。
例如,如果您的应用允许用户标记产品,那么将 CREATE_TAG
和 ADD_TAG_TO_PRODUCT
操作分开会更方便,因为在这种情况下它们同时发生,它们也可能单独发生,并且编写将它们作为不同操作处理的 reducer 会更容易。只要你不滥用这个模式并且不在循环中做这样的事情,你应该没问题。
使操作日志尽可能接近用户交互的历史记录。但是,如果它使 reducer 难以实现,请考虑将一些操作分成几个,如果 UI 更新可以被认为是恰好在一起的两个独立操作。不要陷入任何一个极端。更喜欢 reducer 的清晰度而不是完美的日志,但也更喜欢不在循环中调度以简化 reducer 的清晰度。
为了补充 Dan 的出色回答,当您采用 b) 方式时,您仍然可以像 a 中所说的那样处理状态的不同部分) 通过将根减速器拆分成更小的减速器,比如 Redux docs show。您应该通过组合 reducer 来拆分状态处理,而不是通过任意分派其他操作。正如 Dan 所说,它有助于表达 为什么 .