reducer 改变其动作对象以传达动作的部分处理是否可以接受?
Is it acceptable for a reducer to mutate its action object in order to communicate partial handling of the action?
我正在 React/Redux/Redux Toolkit 中编写一个非常复杂的应用程序,我遇到了一种我不太确定如何处理的情况。我找到了一种方法来做到这一点,但我想知道它是否会导致问题或者是否有更好的方法。简短的版本是我希望 reducer 在不修改状态的情况下与调用者通信,我发现的唯一方法是改变动作。
描述:
为了简单起见,假设我想实现一个水平滚动条(但实际上要复杂得多)。状态包含当前位置,一个上限介于 min
和 max
值之间的数字,UI 绘制一个具有该位置且可以水平单击和拖动的矩形。
Main 属性:如果用户点击并拖拽的距离超过min/max值,那么矩形不会再移动,但是如果用户然后向另一个方向移动,矩形应该等到鼠标回到原来的位置,然后再开始向后移动(就像 most/all 操作系统上的滚动条一样)。
请记住,我的实际用例要复杂得多,我有很多类似的情况,有时在最小值和最大值之间封顶,有时每 100 个像素捕捉一次,有时更复杂的约束取决于图像的各个部分状态等。我想要一个适用于所有此类情况并保留 UI 和逻辑之间的分离的解决方案。
约束条件:
- 我不希望 UI/component/custom 钩子在我们到达 min/max 时负责计算,因为在我的用例中它可能非常复杂并且取决于状态的各个部分.所以 reducer 是唯一知道我们是否到达 min/max.
的地方
- 另一方面,为了实现上面的 Main 属性,我确实需要以某种方式记住我们在矩形上单击的位置,或者有多少像素处理了给定的“拖动”动作,以便知道何时开始向后移动。但我不想将它存储在状态中,因为它实际上是一个不属于该状态的 UI 细节(而且因为我有很多不同的情况需要这样做并且我的状态会变成明显更复杂,不必要的状态更改将对性能造成很大影响。
问题:
所以 reducer 是唯一知道我们是否到达 min/max 的部分,并且 reducer 通常与应用程序其余部分通信的唯一方式是通过状态,但我不想通过国家传达该信息。
解决方案?
我实际上设法找到了解决它的方法,它似乎工作得很好但感觉有点不对劲:改变减速器中的动作对象。
reducer采取“拖10个像素”的动作,意识到它只能拖3个像素,创建一个新的状态,它被拖了3个像素,添加 一个 action.response = 3
字段到操作。
然后在我的自定义挂钩发出“拖动 10 个像素”动作后,它会查看 dispatch
的 return 值的 action.response
字段以了解实际有多少处理后,它会记住与预期值的差异(在这种情况下,它会记住我们距离原始位置 7 个像素)。
这样,如果在下一次 mousemove 时我们拖动 -9 像素,我的自定义钩子可以将该数字添加到它记住的 7 像素,并告诉 reducer 我们只移动了 -2 像素。
在我看来,这个解决方案完美地保留了 UI/logic 的分离:
- reducer 只需要知道我们移动了多少像素,然后 return 新状态以及实际处理了多少像素(通过改变动作)
- 自定义钩子可以记住我们离原始位置有多远(不必知道为什么),然后它会简单地纠正
event.movementX
以补偿减速器在之前的动作,然后将正确的增量发送到减速器。
它也适用于每 100 像素捕捉等情况。
唯一奇怪的是 reducer 改变了动作,我认为这不应该发生,因为它应该是一个纯函数,但到目前为止我找不到任何问题。该应用程序运行正常,Redux Toolkit 没有报错,开发工具也运行良好。
这个解决方案有什么问题吗?
还有其他方法吗?
在技术层面上,我可以看到它是如何工作的。但我也同意它感觉“恶心”。 非常 从技术上讲,改变操作本身可以称为“副作用”,尽管它不会有意义地破坏应用程序的其余部分。
听起来好像这里的逻辑关键位更多地处于“调度操作”级别。我认为您可能会在分派之前和之后调用 getState()
来比较结果,并以这种方式导出所需的额外数据。事实上,这可能是 thunk 的一个很好的用例:
const processDragEvent = (dragAmountPixels: number) => {
return (dispatch, getState) => {
const stateBefore = getState();
dispatch(dragMoved(dragAmountPixels));
const stateAfter = getState();
const actualAmountChanged = selectActualDragAmount(stateBefore, stateAfter);
// can return a result from the thunk
return actualAmountChanged
}
}
// later, in a hook
const useMyCustomDragBehavior = () => {
const dispatch = useDispatch();
const doSomeDragging = (someDragValue: number) => {
const actualAmountDragged = dispatch(processDragEvent (someDragValue));
// do something useful with this info here
}
}
这样只有 UI 层与更改有关。
我正在 React/Redux/Redux Toolkit 中编写一个非常复杂的应用程序,我遇到了一种我不太确定如何处理的情况。我找到了一种方法来做到这一点,但我想知道它是否会导致问题或者是否有更好的方法。简短的版本是我希望 reducer 在不修改状态的情况下与调用者通信,我发现的唯一方法是改变动作。
描述:
为了简单起见,假设我想实现一个水平滚动条(但实际上要复杂得多)。状态包含当前位置,一个上限介于 min
和 max
值之间的数字,UI 绘制一个具有该位置且可以水平单击和拖动的矩形。
Main 属性:如果用户点击并拖拽的距离超过min/max值,那么矩形不会再移动,但是如果用户然后向另一个方向移动,矩形应该等到鼠标回到原来的位置,然后再开始向后移动(就像 most/all 操作系统上的滚动条一样)。
请记住,我的实际用例要复杂得多,我有很多类似的情况,有时在最小值和最大值之间封顶,有时每 100 个像素捕捉一次,有时更复杂的约束取决于图像的各个部分状态等。我想要一个适用于所有此类情况并保留 UI 和逻辑之间的分离的解决方案。
约束条件:
- 我不希望 UI/component/custom 钩子在我们到达 min/max 时负责计算,因为在我的用例中它可能非常复杂并且取决于状态的各个部分.所以 reducer 是唯一知道我们是否到达 min/max. 的地方
- 另一方面,为了实现上面的 Main 属性,我确实需要以某种方式记住我们在矩形上单击的位置,或者有多少像素处理了给定的“拖动”动作,以便知道何时开始向后移动。但我不想将它存储在状态中,因为它实际上是一个不属于该状态的 UI 细节(而且因为我有很多不同的情况需要这样做并且我的状态会变成明显更复杂,不必要的状态更改将对性能造成很大影响。
问题:
所以 reducer 是唯一知道我们是否到达 min/max 的部分,并且 reducer 通常与应用程序其余部分通信的唯一方式是通过状态,但我不想通过国家传达该信息。
解决方案?
我实际上设法找到了解决它的方法,它似乎工作得很好但感觉有点不对劲:改变减速器中的动作对象。
reducer采取“拖10个像素”的动作,意识到它只能拖3个像素,创建一个新的状态,它被拖了3个像素,添加 一个 action.response = 3
字段到操作。
然后在我的自定义挂钩发出“拖动 10 个像素”动作后,它会查看 dispatch
的 return 值的 action.response
字段以了解实际有多少处理后,它会记住与预期值的差异(在这种情况下,它会记住我们距离原始位置 7 个像素)。
这样,如果在下一次 mousemove 时我们拖动 -9 像素,我的自定义钩子可以将该数字添加到它记住的 7 像素,并告诉 reducer 我们只移动了 -2 像素。
在我看来,这个解决方案完美地保留了 UI/logic 的分离:
- reducer 只需要知道我们移动了多少像素,然后 return 新状态以及实际处理了多少像素(通过改变动作)
- 自定义钩子可以记住我们离原始位置有多远(不必知道为什么),然后它会简单地纠正
event.movementX
以补偿减速器在之前的动作,然后将正确的增量发送到减速器。
它也适用于每 100 像素捕捉等情况。
唯一奇怪的是 reducer 改变了动作,我认为这不应该发生,因为它应该是一个纯函数,但到目前为止我找不到任何问题。该应用程序运行正常,Redux Toolkit 没有报错,开发工具也运行良好。
这个解决方案有什么问题吗?
还有其他方法吗?
在技术层面上,我可以看到它是如何工作的。但我也同意它感觉“恶心”。 非常 从技术上讲,改变操作本身可以称为“副作用”,尽管它不会有意义地破坏应用程序的其余部分。
听起来好像这里的逻辑关键位更多地处于“调度操作”级别。我认为您可能会在分派之前和之后调用 getState()
来比较结果,并以这种方式导出所需的额外数据。事实上,这可能是 thunk 的一个很好的用例:
const processDragEvent = (dragAmountPixels: number) => {
return (dispatch, getState) => {
const stateBefore = getState();
dispatch(dragMoved(dragAmountPixels));
const stateAfter = getState();
const actualAmountChanged = selectActualDragAmount(stateBefore, stateAfter);
// can return a result from the thunk
return actualAmountChanged
}
}
// later, in a hook
const useMyCustomDragBehavior = () => {
const dispatch = useDispatch();
const doSomeDragging = (someDragValue: number) => {
const actualAmountDragged = dispatch(processDragEvent (someDragValue));
// do something useful with this info here
}
}
这样只有 UI 层与更改有关。