Reactjs React Hooks:确保useEffect仅在自定义挂钩的数组参数的内容更改时运行的惯用方法
我正在React中创建一个自定义钩子,它为给定的一组事件设置一个事件侦听器。存在一组默认的事件,在大多数用例中,该自定义挂钩的使用者不需要自定义这些事件。通常,我希望在安装组件时添加事件侦听器,在卸载组件时删除事件侦听器。但是,根据hooks的原则(以及Reactjs React Hooks:确保useEffect仅在自定义挂钩的数组参数的内容更改时运行的惯用方法,reactjs,react-hooks,use-effect,Reactjs,React Hooks,Use Effect,我正在React中创建一个自定义钩子,它为给定的一组事件设置一个事件侦听器。存在一组默认的事件,在大多数用例中,该自定义挂钩的使用者不需要自定义这些事件。通常,我希望在安装组件时添加事件侦听器,在卸载组件时删除事件侦听器。但是,根据hooks的原则(以及eslint(react hooks/deps)lint规则),我希望优雅地处理对要观看的事件列表的更改。用React钩子实现这一点最惯用的方法是什么 假设我只想删除所有事件侦听器,并在事件列表更改时重新添加它们,我可以尝试以下操作: const
eslint(react hooks/deps)
lint规则),我希望优雅地处理对要观看的事件列表的更改。用React钩子实现这一点最惯用的方法是什么
假设我只想删除所有事件侦听器,并在事件列表更改时重新添加它们,我可以尝试以下操作:
const useventwatcher=(
间隔=5000,
事件=['mousemove','keydown','wheel']
) => {
const timerIdRef=useRef();
useffect(()=>{
常量重置间隔=()=>{
if(timerIdRef.current){
清除间隔(timerIdRef.current);
}
timerIdRef.current=setInterval(()=>{
log(`${interval}毫秒,没有${events.join(',')}事件!`);
},间隔时间)
};
events.forEach(event=>window.addEventListener(event,resetInterval));
//不想错过第一次没有休息的时间
//给定的事件触发(因此无法在每次渲染后运行效果!)
重置间隔();
return()=>{
events.forEach(event=>window.removeEventListener(event,resetInterval));
};
},[事件,间隔];
}
不幸的是,这将无法发挥预期的作用。请注意,我想为
events
参数提供一个默认值。使用当前方法执行此操作意味着每次自定义挂钩运行时,事件
都指向不同的引用,这意味着效果也会每次运行(由于浅层依赖关系比较)。理想情况下,我希望效果取决于数组的内容,而不是引用。实现这一点的最佳方法是什么?您可以在两个不同的useffects
中区分两种副作用
useffect
中运行初始resetInterval
。
您需要运行一次,并且可以使用依赖项[]
但是,您需要在
useffect
之外提取resetInterval
另一个问题是,现在每次渲染时都会重新创建resetInterval
。因此,您可以将其包装在
useCallback
中第一个
useffect
取决于resetInterval
(这会导致useffect
在加载时运行一次,因此将调用resetInterval
)useffect
中的所有事件
,依赖于[事件,间隔,重置间隔]
,如“eslint(react hooks/deps)lint规则”所建议的那样const useventwatcher=(
间隔=2000,
事件=[“mousemove”、“keydown”、“wheel”]
) => {
const timerIdRef=useRef();
const resetInterval=useCallback(()=>{
if(timerIdRef.current){
清除间隔(timerIdRef.current);
}
timerIdRef.current=setInterval(()=>{
console.log(
`通过了${interval}秒,没有${events.join(“,”}事件`
);
},间隔);
},[事件,间隔];
useffect(()=>{
重置间隔();
},[resetInterval]);
useffect(()=>{
events.forEach(event=>window.addEventListener(event,resetInterval));
return()=>{
events.forEach(event=>window.removeEventListener(event,resetInterval));
};
},[事件、间隔、重置间隔];
};
查看工作页面(出于演示目的,我将间隔设置为2秒)
当您移动鼠标、滚轮或按键时,控制台日志将不会显示。一旦没有触发这些事件,您将看到控制台日志消息
因此我们需要将
事件
包含到DEP中,但我们肯定不希望出现无休止的渲染循环
选项1:使用JSON.stringify
或类似方法将字符串作为依赖项而不是数组传递
function useEventWatcher(events = ['click'])
useEffect(() => {
}, [JSON.stringifiy(events.sort())])
但是,ESLint仍会抱怨,因此请抑制它或使用反字符串化:
const eventsStringified = JSON.stringify(events.sort());
useEffect(() => {
const eventsUnstringified = JSON.parse(eventsStringified);
}, [eventStringified]);
选项2:将设置默认值移动到usemo
。因此,当未传递事件
参数时,默认值将参考相同(因此它是未定义的
)
为什么要避免在deps中列出
事件
?若用户最终想要指定事件,那个么将['scroll']
更改为['click']
处理错误的事件可能会让人大吃一惊。免费处理这样的案件不是更好吗?请参阅我的编辑以获得澄清。
function useEventWatcher(events) {
const eventsOrDefault = useMemo(() => eventsPassed || ['click'], [events]);
useEffect(() => {
}, [eventsOrDefault]);
}