Ionic Capacitor深层链接appUrlOpen事件触发10次以上重复执行如何解决
问题根因
- 你在useEffect中注册了Capacitor全局事件监听器,但没有添加清理逻辑移除旧监听器。每次组件重新挂载、依赖项变化时,旧的监听器会一直保留,同时新增一个监听器,最终同一个事件触发时会执行多次回调。
- 依赖项
startActionApprovalProcess没有稳定引用:如果useCamera返回的这个函数没有用useCallback包裹,每次组件渲染都会生成新的函数实例,触发useEffect重复执行,不断新增监听器。 - livereload开发模式下,热更新会重新加载组件,进一步加剧监听器叠加的问题,从你的日志中能看到累计注册了10个监听器,所以事件会触发10次。
解决步骤
1. 给useEffect添加监听器清理逻辑
Capacitor的addListener方法返回一个带remove方法的监听器实例,你需要在useEffect的清理函数中调用remove方法销毁旧监听器,避免叠加。修改后的组件代码如下:
import { useEffect, useRef } from "react"; import { App, URLOpenListenerEvent, PluginListenerHandle } from "@capacitor/app"; import { useCamera } from "src/hooks"; const AppUrlListener: React.FC<any> = () => { const { startActionApprovalProcess } = useCamera(); // 存储监听器实例用于清理 const appUrlOpenListenerRef = useRef<PluginListenerHandle | null>(null); const appStateChangeListenerRef = useRef<PluginListenerHandle | null>(null); // 去重用,记录最近处理的链接和时间 const lastProcessedUrlRef = useRef<{url: string, time: number} | null>(null); useEffect(() => { // 注册监听器 const registerListeners = async () => { // 先清理旧的监听器,避免重复 if (appUrlOpenListenerRef.current) { await appUrlOpenListenerRef.current.remove(); } if (appStateChangeListenerRef.current) { await appStateChangeListenerRef.current.remove(); } appUrlOpenListenerRef.current = await App.addListener("appUrlOpen", (event: URLOpenListenerEvent) => { const actionUrl = event.url.replace("myrecheck", "https"); // 1s内相同的url直接跳过,避免重复执行 const now = Date.now(); if (lastProcessedUrlRef.current && lastProcessedUrlRef.current.url === actionUrl && now - lastProcessedUrlRef.current.time < 1000) { return; } lastProcessedUrlRef.current = {url: actionUrl, time: now}; startActionApprovalProcess(actionUrl); }); appStateChangeListenerRef.current = await App.addListener("appStateChange", event => { console.log("appStateChange:", JSON.stringify(event)); }); }; registerListeners(); // 组件卸载/依赖更新时清理监听器 return () => { const removeListeners = async () => { if (appUrlOpenListenerRef.current) { await appUrlOpenListenerRef.current.remove(); appUrlOpenListenerRef.current = null; } if (appStateChangeListenerRef.current) { await appStateChangeListenerRef.current.remove(); appStateChangeListenerRef.current = null; } }; removeListeners(); }; }, [startActionApprovalProcess]); return null; }; export default AppUrlListener;
2. 稳定依赖项引用
修改useCamera自定义Hook,给startActionApprovalProcess函数包裹useCallback,明确声明它的依赖项,保证它的引用不会在无意义的渲染时发生变化,减少useEffect的触发次数,示例如下:
// 在useCamera hook中修改 const startActionApprovalProcess = useCallback(async (url: string) => { // 原有业务逻辑 }, [/* 函数内部用到的所有外部依赖变量 */]);
3. 可选:调整监听器注册时机
如果你希望监听器全局只注册一次,可以将注册逻辑移到App.tsx的顶层useEffect中,依赖项设为空数组,这样只会在应用初始化时执行一次,不会因为组件更新重复注册。
验证
修改完成后先在开发环境测试,看日志中App.addListener的输出是否只剩一次,深层链接唤起后qr-decode.ts的日志也只会输出一次,告警弹窗不会重复弹出。生产环境没有livereload的热更影响,修复后不会再出现该问题。
内容的提问来源于stack exchange,提问作者Byurhan Beyzat
相关产品推荐
相关产品推荐

