React开发Chrome扩展时如何停止event listener避免过度重渲染?
问题原因
你将chrome.runtime.onMessage.addListener直接写在React组件的函数体内,未配置清理逻辑:
- React组件每次重渲染(含state更新触发的重渲染)都会执行一遍函数体内的所有代码,相当于每更新一次URL就新增一个重复的消息监听器
- 新增的监听器收到消息后会再次触发state更新,进而触发新一轮重渲染、新增更多监听器,陷入死循环,最终导致日志指数级打印、页面崩溃
“将回调提取为顶层函数”的含义
不要把回调逻辑以匿名函数的形式直接写在addListener的参数中,要单独将其定义为可被引用的具名函数,后续才能通过同一个函数引用移除旧的监听器,避免重复注册。
修复方案
结合React的useEffect和useCallback处理监听器的注册与清理即可,代码示例如下:
import { useEffect, useState, useCallback } from 'react' function Popup() { const [currentUrl, setCurrentUrl] = useState('') const [isLoggedIn, setIsLoggedIn] = useState(false) // 单独定义消息处理回调,用useCallback包裹保证依赖不变时函数引用固定 const handleMessage = useCallback((msg, sender) => { if (msg.message === 'currentUrl') { console.log('this is triggered') setCurrentUrl(msg.url) if (isLoggedIn) { extractZendeskId(msg.url) } } }, [isLoggedIn]) // 依赖项填写回调中用到的所有外部变量 useEffect(() => { // 组件挂载时注册监听器 chrome.runtime.onMessage.addListener(handleMessage) // 组件卸载/依赖更新时移除旧监听器,避免重复注册 return () => { chrome.runtime.onMessage.removeListener(handleMessage) } }, [handleMessage]) // 其余组件逻辑 return <div>{/* 弹窗UI */}</div> }
可选优化方案
Chrome扩展的popup是临时实例,仅在点击扩展图标打开时创建,关闭后自动销毁。你也可以直接放弃被动监听background的消息,改为popup打开时主动拉取当前URL,从根源上规避监听器重复注册的问题:
import { useEffect, useState } from 'react' function Popup() { const [currentUrl, setCurrentUrl] = useState('') const [isLoggedIn, setIsLoggedIn] = useState(false) useEffect(() => { // 弹窗打开时主动查询当前活跃标签页的URL chrome.tabs.query({active: true, currentWindow: true}, (tabs) => { const activeTab = tabs[0] if (!activeTab?.url) return setCurrentUrl(activeTab.url) if (isLoggedIn) { extractZendeskId(activeTab.url) } }) }, [isLoggedIn]) // 其余组件逻辑 }
内容的提问来源于stack exchange,提问作者InquisitiveTom
相关产品推荐
相关产品推荐

