未移除Preact中Chrome DevTools网络监听器致重复触发的原因
我正在开发一款Chrome扩展,使用Preact库。初始状态和useEffect代码如下:
const [requests, setRequests] = useState([]); useEffect(() => { function processRequest(request) { // if filter is unset if (filter == "") { setRequests((requests) => [...requests, request]); } else { // filter request url using wildcard if (request.request.url.includes(filter)) { setRequests((requests) => [...requests, request]); } } } chrome.devtools.network.onRequestFinished.addListener(processRequest); return () => { } }, []);
把requests绑定到页面列表后测试发现,一次页面刷新后列表长度达到20000,但实际只有约40个请求,大量请求重复出现,单个请求通常被保存数百次。
意识到未移除事件监听器后,我给useEffect加上了清理逻辑:
return () => { chrome.devtools.network.onRequestFinished.removeListener(processRequest); }
修改后恢复正常。想了解未移除监听器为何会导致监听器被触发数百次?
原因解析
核心原因是重复绑定事件监听器,结合Preact组件的挂载/卸载逻辑和Chrome DevTools的特点,具体逻辑如下:
监听器越绑越多
组件首次挂载时,useEffect(依赖为空数组,仅首次挂载执行)会给chrome.devtools.network.onRequestFinished绑定一个processRequest函数。但如果组件因为切换DevTools面板、刷新页面等原因卸载后重新挂载,之前绑定的监听器因为没写清理逻辑,会一直留在事件上;新的挂载又会执行useEffect,绑定一个全新的processRequest函数。次数多了,事件上就会绑定成百上千个监听器实例。单个请求触发所有监听器
每当一个网络请求完成时,onRequestFinished事件会触发一次,但此时所有绑定的监听器都会执行——每个监听器都会把同一个请求添加到requests数组里,所以单个请求会被重复保存数百次,最终数组长度远大于实际请求数。清理函数解决重复绑定
加上removeListener的清理逻辑后,组件卸载时会移除对应的processRequest监听器。这样每次组件重新挂载时,只会存在一个有效的监听器,自然就不会出现重复添加请求的问题了。
额外提醒:你的代码里useEffect依赖数组是空的,但内部用到了filter变量,这会导致filter更新时,监听器里的filter还是旧值(闭包陷阱),建议把filter加入useEffect的依赖数组,同时确保每次依赖变化时先移除旧监听器、再绑定新的,避免过滤逻辑失效。
内容的提问来源于stack exchange,提问作者Max Luan

