// frontend/composables/useAliveGuard.js // // 用途:UniApp iOS WebView 的 JS Framework 在原生异步回调回来时,如果页面已销毁, // 调 n.fireEvent 会报 "undefined is not an object (evaluating 'n.fireEvent')" // (warning 级别,不影响原生操作结果,但日志噪音大且难定位) // // 解法:在 setup() 里创建 alive 守卫,页面卸载/组件销毁时翻成 false; // 所有原生异步回调(uni.* / plus.io.* / plus.nativeObj.* 的 success/fail/complete) // 包一层 guard,守卫死亡后直接吞掉结果,不更新 state、不弹 toast、不继续后续链式调用。 // // 注意:我们不取消原生操作本身 — 用户已经触发了上传/保存/文件读取,这些操作的实际副作用 // (网络请求发出、文件落盘)应该正常完成。只是 JS 这一侧不应当再触碰一个已死页面的 state。 // // 用法: // import { useAliveGuard } from '@/composables/useAliveGuard.js' // // const { isAlive, die, guard } = useAliveGuard() // // uni.uploadFile({ // url, filePath, // success: guard((res) => { /* 仅 alive=true 时执行 */ }), // fail: guard((err) => { /* 同上 */ }), // complete: guard(() => { /* 同上 */ }) // }) // // // plus.io 链式回调: // plus.io.resolveLocalFileSystemURL( // path, // guard((entry) => { entry.file(guard((file) => { ... })) }), // guard((err) => { ... }) // ) // // // 主动检查: // if (!isAlive()) return // // 自动绑定生命周期: // - 页面上下文(setup 里当前实例是 page):绑定 onUnload(页面关闭时死) // - 组件上下文(setup 里当前实例是 component):绑定 onBeforeUnmount(v-if=false / 父组件销毁时死) // - 两者都不存在(罕见):返回的 alive 永远为 true(等同不守卫,留给调用方自行 die()) import { onBeforeUnmount } from 'vue' import { onUnload } from '@dcloudio/uni-app' /** * @returns {{ * isAlive: () => boolean, * die: () => void, * guard: (fn: T) => T * }} */ export function useAliveGuard() { let alive = true let bound = false // 优先 onUnload(页面级生命周期,从 @dcloudio/uni-app 导入) // 页面里调 onUnload 是注册的页面 onUnload 钩子;组件里调可能抛 "onUnload is not a function" try { onUnload(() => { alive = false }) bound = true } catch (_) { /* 组件上下文,onUnload 不可用,下面兜底 */ } // 兜底 onBeforeUnmount(Vue 组件级生命周期) if (!bound) { try { onBeforeUnmount(() => { alive = false }) } catch (_) { /* 都不在 setup 里调,保持 alive=true,调用方自行 die() */ } } return { isAlive: () => alive, /** 主动翻成 false(罕见用 — 大多数场景生命周期钩子已经处理) */ die: () => { alive = false }, /** * 包装回调:页面/组件已死时 no-op,活着时透传执行 * 非函数参数原样返回,方便链式写:guard(maybeUndefined) */ guard(fn) { if (typeof fn !== 'function') return fn return function guardedCallback(...args) { if (!alive) return undefined return fn.apply(this, args) } } } }