topfans/frontend/composables/useAliveGuard.js
2026-07-13 14:56:08 +08:00

89 lines
3.1 KiB
JavaScript

// 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: <T extends Function>(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)
}
}
}
}