React/Redux、useEffect/useSelector、过时闭包和事件监听器
我有一个 React/Redux 类组件,我正在将其转换为功能组件。
以前,它有一个 componentDidMount
回调,该回调为从应用程序中其他位置调度的自定义事件添加了一个事件侦听器。它正在被 useEffect
取代。事件侦听器在触发时调用该组件中其他位置的方法。
此方法执行的操作取决于从选择器检索的值。最初,我传递了 useEffect
一个空的依赖项数组,因此它只会在挂载上添加事件侦听器。然而,显然这会导致选择器值周围的闭包过时。一个可行的解决方案是将选择器作为依赖项传递给它 - 但是,这会导致每次选择器的值发生更改时都会删除/重新添加侦听器,这不太好。
我正在尝试考虑一种解决方案,该解决方案仅添加一次事件侦听器,同时允许被调用的方法访问选择器的当前值。
示例代码:
const currentValue = useSelector(state => getValue(state));
useEffect(() => {
document.addEventListener('my.custom.event', handleEvent);
return(() => document.removeEventListener('my.custom.event', handleEvent));
}, []);
const handleEvent = () => {
console.log(currentValue)
}
按原样,这会在 currentValue
周围创建一个陈旧的闭包,以便在事件触发时,记录的值不一定是最新的。
将 useEffect
中的 []
更改为 [currentValue]
会产生预期的行为,但会在每次更改 < 时删除/重新添加事件侦听器代码>当前值。
由于这不是组件的状态值,因此无法使用像 console.log(currentValue => console.log(currentValue)) 这样的回调来访问最新值。我还尝试使用 useRef
来保留该值,但我相信每次选择器的值发生变化时我都需要某种方法来更新 ref 值,这并不是一个解决方案。
在实际组件中,currentValue
的值在 Redux 中被其他组件修改,因此将其更改为状态值实际上也是行不通的。
我想知道:
- 是否有解决方案可以在被调用的方法中刷新选择器的值;
- 唯一的解决方案是否是在依赖项更改时删除/重新添加侦听器,或者;
- 是否最好将此组件保留为类组件并完全绕过此问题(
componentDidMount
不会遇到过时的关闭问题。)
I have a React/Redux class component which I'm converting to a functional component.
Previously, it had a componentDidMount
callback which added an event listener for a custom event dispatched from elsewhere in the app. This is being replaced by useEffect
. The event listener, on triggering, calls a method elsewhere in this component.
This method performs actions which depend on the values retrieved from a selector. Initially I was passing useEffect
an empty array of dependencies so it would only add the event listener on mount. However, obviously this results in a stale closure around the selector values. A working solution is to pass it the selector as a dependency – however, this results in the listener being removed/re-added every time the value of the selector changes, which isn't great.
I'm trying to think of a solution which only adds the event listener the one time while allowing the called method to access the current value of the selector.
Example code:
const currentValue = useSelector(state => getValue(state));
useEffect(() => {
document.addEventListener('my.custom.event', handleEvent);
return(() => document.removeEventListener('my.custom.event', handleEvent));
}, []);
const handleEvent = () => {
console.log(currentValue)
}
As is, this creates a stale closure around currentValue
so that on event trigger, the value logged isn't necessarily the most recent.
Changing []
to [currentValue]
in useEffect
results in the expected behavior, but removes/re-adds the event listener on every change of currentValue
.
Since this isn't a state value of the component, there's no option to use a callback like console.log(currentValue => console.log(currentValue))
to access the most recent value. I also played around with using useRef
to hold onto the value, but I believe I'd need some way to update the ref value every time the selector's value changes, which isn't much of a solution.
In the actual component, the value of currentValue
is modified in Redux by other components so changing it into a state value isn't really workable either.
I'm wondering:
- whether there's a solution to refreshing the value of the selector in the called method;
- whether the only solution is to remove/re-add the listener on dependency change, or;
- whether it's best to just leave this component as a class component and bypass this issue entirely (
componentDidMount
doesn't suffer from the stale closure issue.)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
useRef
方法通常是此问题的解决方案,但您需要另一个useEffect
来使用currentValue
更新引用:但是,您还可以从
useEffect
中提取整个函数,并在钩子中使用它,这样您就可以轻松创建用于事件处理的自定义钩子:使用钩子:
The
useRef
method is usually the solution for this problem, but you'll need anotheruseEffect
to update the ref with thecurrentValue
:However, you can also extract the entire function out of the
useEffect
, and use it in a hook, so you can easily create a custom hook for event handling:Using the hook:
为什么这是一个问题?它是否以任何可衡量的方式影响您的应用程序的性能?从不变性的角度来看,如果值发生变化,则事件侦听器会执行不同的操作,因此简单地用不同的事件侦听器替换事件侦听器是有意义的。
如果该值每秒更改多次(例如在每一帧或每次鼠标移动时),那么它确实会对性能产生较小的影响,在这种情况下,Ori Drori 的解决方案可能是一个不错的解决方案,但它使代码更难理解,并且性能增益很小。
但是,如果该值每秒仅更改几次或频率较低,那么我根本不用担心添加和删除事件侦听器。
Why is it a problem? Does it affect performance of your app in any measurable way? From an immutability point of view, if the value change, then the event listener does something different, so it makes sense to simply replace the event listener by a different one.
If the value changes many times a second (for instance at every frame or every mousemove), then it does create a small performance hit, in which case Ori Drori's solution might be a good one, but it makes the code harder to understand and the performance gain is pretty small.
But if the value only changes a few times a second or less often, I would not worry at all about adding and removing the event listener.