小程序端必须用队列而非直接覆盖,因原生 showToast/showModal 不支持并发调用,后调用会中断前调用;需用 Vue 实例 + Promise 链实现串行队列,动态挂载到当前页根节点并及时卸载,支持优先级与生命周期暂停。

为什么小程序端必须用队列,而不是直接覆盖显示
小程序的 showToast 和 showModal 原生 API 不支持并发调用——后一次调用会直接中断前一次,导致用户看不到第一条提示。比如网络请求失败弹出错误,紧接着又触发登录过期提示,结果只显示后者。这不是 bug,是微信小程序底层限制。所以必须自己维护一个队列,让弹窗“排队等播”,否则业务逻辑一复杂就丢消息。
如何用 Vue 实例 + Promise 链实现可靠队列
不能依赖 Vuex 或全局 state 简单 push/pop,因为小程序页面实例隔离,跨页面状态不同步;也不能靠定时器轮询,容易漏判关闭时机。实测最稳的方式是:每个弹窗封装成一个返回 Promise 的函数,队列用数组 + 串行执行器控制。
- 每次调用弹窗方法(如
showGlobalAlert)都返回一个Promise,resolve 值为用户操作结果(confirm/cancel/close) - 内部维护一个
queue: Array<() => Promise<any>>,新任务 push 进去,但只允许一个在执行 - 执行器用
async/await+queue.shift()保证串行,上一个弹窗close回调触发下一个启动 - 小程序端特别注意:必须监听
onHide和onUnload,防止页面卸载时弹窗还卡在队列里
动态挂载组件时如何避免层级错乱和内存泄漏
直接 createApp 挂到 document.body 在小程序里不可行——它没有真实 DOM;必须挂到当前页面实例的根容器节点。否则会出现:弹窗显示在其他页面上、遮罩不生效、点击穿透等问题。
- 用
getCurrentPages().pop().$el获取当前页真实容器,再appendChild动态创建的弹窗节点 - 每个弹窗实例必须绑定
onClose回调,并在回调里主动unmount()+removeChild(),否则多次打开后内存持续增长 - 不要复用同一个 Vue 实例——每次都要
createApp新实例,否则 props 响应式失效或状态残留 - 小程序真机调试时,
z-index失效很常见,务必用position: fixed+top: 0+width: 100vw强制撑满视口
队列中断与优先级怎么处理才不翻车
纯 FIFO 队列在实际业务中不够用。比如用户正在输入表单,突然弹出「网络异常」,这时如果后面排着一个「优惠券到账」提示,显然应该跳过或降级——但不能简单清空队列,否则丢失关键操作反馈。
- 给每个弹窗配置
priority: number(如 error=10, info=1),入队前先按 priority 插入合适位置,而非末尾 push - 提供
clearQueue(type?: 'error' | 'info')接口,允许按类型清理,而不是全清 - 关键场景(如登录态失效)要支持
force: true参数,强制插入队首并中断当前弹窗(调用instance.unmount()+reject当前 Promise) - 小程序生命周期钩子中(如
onHide)需暂停队列执行,onShow再恢复,否则切后台时弹窗卡住


















