React回调中读取chrome.tabs相关State值为undefined的问题
问题:Chrome扩展React Hook回调无法读取最新状态
在Chrome扩展的自定义Hook中,调用chrome.tabs.onUpdated.addListener的回调函数时,尝试读取React状态activeTabId,但即使之前已通过setActiveTabId设置了值,该状态仍为undefined,导致无法触发currentTab的更新逻辑。
自定义Hook代码如下:
export const useCurrentTab = () => { const [currentTab, setCurrentTab] = useState<chrome.tabs.Tab | undefined>(undefined) const [activeTabId, setActiveTabId] = useState<number | undefined>(undefined) const getActiveTab = async () => { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }) console.log(`Setting current tab id to ${tab?.id}`) setActiveTabId((prevActiveTabId) => tab?.id || prevActiveTabId) return tab } const updateCurrentTab = ( tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab ) => { console.log("updateCurrentTab", activeTabId, currentTab, tabId); if (changeInfo.status === 'complete' && tabId === activeTabId) { console.log(`Tab ${tabId} status changed to ${changeInfo.status}`) setCurrentTab(tab) } } useEffect(() => { getActiveTab().then((tab) => { if (tab && tab.status === 'complete') { setCurrentTab(tab) } }) chrome.tabs.onUpdated.addListener(updateCurrentTab) return () => { chrome.tabs.onUpdated.removeListener(updateCurrentTab) } }, []) return currentTab }
控制台日志显示:
current-tab.tsx:15 Setting current tab id to 1494595641 current-tab.tsx:25 updateCurrentTab undefined undefined 1494595641
原因分析
问题出在闭包捕获:useEffect的依赖数组为空,意味着它只会在组件首次渲染时执行一次。此时updateCurrentTab函数捕获的是activeTabId和currentTab的初始值(都是undefined)。后续调用setActiveTabId更新状态时,组件会重新渲染,但useEffect不会再次执行,绑定的监听器还是旧的updateCurrentTab函数,它始终读取的是初始状态值。
解决方案
方案1:使用useRef保存最新状态
用useRef存储最新的activeTabId和currentTab,ref的current属性是可变的,不会被闭包捕获固定值,回调可以直接读取最新值:
export const useCurrentTab = () => { const [currentTab, setCurrentTab] = useState<chrome.tabs.Tab | undefined>(undefined) const [activeTabId, setActiveTabId] = useState<number | undefined>(undefined) // 用ref保存最新状态 const activeTabIdRef = useRef<number | undefined>(undefined) const currentTabRef = useRef<chrome.tabs.Tab | undefined>(undefined) // 状态更新时同步ref的值 useEffect(() => { activeTabIdRef.current = activeTabId currentTabRef.current = currentTab }, [activeTabId, currentTab]) const getActiveTab = async () => { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }) console.log(`Setting current tab id to ${tab?.id}`) setActiveTabId((prevActiveTabId) => tab?.id || prevActiveTabId) return tab } const updateCurrentTab = ( tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab ) => { // 读取ref的current属性获取最新值 console.log("updateCurrentTab", activeTabIdRef.current, currentTabRef.current, tabId); if (changeInfo.status === 'complete' && tabId === activeTabIdRef.current) { console.log(`Tab ${tabId} status changed to ${changeInfo.status}`) setCurrentTab(tab) } } useEffect(() => { getActiveTab().then((tab) => { if (tab && tab.status === 'complete') { setCurrentTab(tab) } }) chrome.tabs.onUpdated.addListener(updateCurrentTab) return () => { chrome.tabs.onUpdated.removeListener(updateCurrentTab) } }, []) return currentTab }
方案2:让useEffect依赖状态,重新绑定监听器
将updateCurrentTab用useCallback包裹并添加依赖,同时让useEffect依赖updateCurrentTab,这样每次状态变化时,useCallback会生成新的函数,useEffect会重新绑定监听器:
export const useCurrentTab = () => { const [currentTab, setCurrentTab] = useState<chrome.tabs.Tab | undefined>(undefined) const [activeTabId, setActiveTabId] = useState<number | undefined>(undefined) const getActiveTab = async () => { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }) console.log(`Setting current tab id to ${tab?.id}`) setActiveTabId((prevActiveTabId) => tab?.id || prevActiveTabId) return tab } // 用useCallback包裹,添加状态依赖 const updateCurrentTab = useCallback(( tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab ) => { console.log("updateCurrentTab", activeTabId, currentTab, tabId); if (changeInfo.status === 'complete' && tabId === activeTabId) { console.log(`Tab ${tabId} status changed to ${changeInfo.status}`) setCurrentTab(tab) } }, [activeTabId, currentTab]) // 依赖状态变化 useEffect(() => { getActiveTab().then((tab) => { if (tab && tab.status === 'complete') { setCurrentTab(tab) } }) chrome.tabs.onUpdated.addListener(updateCurrentTab) return () => { chrome.tabs.onUpdated.removeListener(updateCurrentTab) } }, [updateCurrentTab]) // 依赖updateCurrentTab,状态变化时重新执行 return currentTab }
注意:方案2中每次状态变化都会解绑并重新绑定监听器,Chrome API能正常处理这种情况,但如果状态频繁变化,可能存在轻微性能影响,方案1的ref方式更高效。
内容的提问来源于stack exchange,提问作者SamHoque
相关产品推荐
相关产品推荐

