1、主要问题
在使用uniapp进行小程序开发的时候发现一个问题,当调用uni.showToast后立即调用uni.hideLoading,uni.showToast会被立即关闭,导致弹出的提示一闪即逝,猜测小程序的toast和loading可能使用的是同一个组件
/**
* 1.前面一个任务执行完成,后面的 'toast' | 'showLoading' | 'hideLoading' 才能执行
* 2.hideLoading 执行逻辑 -> 当有提示弹窗(toast)的时候,hideLoading必须等到toast完成才能执行,没有toast可以立即执行关闭showLoading,
* 3.toast 执行逻辑 -> 当有提示弹窗(toast)的时候,toast必须等到上一个toast完成才能执行
* 4.showLoading 执行逻辑 -> 当有提示弹窗(toast)的时候,showLoading必须等到上一个toast完成才能执行,没有toast,就直接执行
*
* 注意,showLoading 不会覆盖toast, toast不会覆盖前面的toast,hideLoading会立即关闭前面的showLoading,不会对关闭toast
*/
interface TaskItem {
fun: 'toast' | 'showLoading' | 'hideLoading'
title?: string
duration?: number
resolve?: (value: any) => void // 任务执行完成后的回调
reject?: (reason: any) => void
}
class Tip {
// 任务队列
taskList: Array<TaskItem>
duration: number
// 当前执行的任务
curTask: TaskItem | null
constructor() {
// 任务队列
this.taskList = []
// toast弹出时间
this.duration = 1500
// 当前执行的任务
this.curTask = null
}
// 添加任务
pushTask(obj: TaskItem) {
// 如果是hideLoading,需要立即执行
if (obj.fun === 'hideLoading') {
this._runHideLoadingTask()
} else {
this.taskList.push(obj)
if (!this.curTask) this._runTask()
}
}
// 添加提示弹框
toast(title: string) {
return new Promise((resolve, reject) => {
this.pushTask({ fun: 'toast', title, reject, resolve })
})
}
// 添加加载弹框,不手动调用hideLoading,弹窗会再6000后关闭
showLoading(title = '加载中', duration = 6000) {
this.pushTask({ fun: 'showLoading', title, duration })
}
// 添加隐藏弹框
hideLoading() {
this.pushTask({ fun: 'hideLoading' })
}
setCurTask(task: TaskItem | null) {
this.curTask = task
}
// 小提示弹框
_runToastTask() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const _this = this
return new Promise((resolve, reject) => {
uni.showToast({
title: `${_this.curTask?.title}`,
icon: 'none',
duration: _this.duration,
success(e) {
setTimeout(() => {
_this.setCurTask(null)
_this.taskList.shift()
resolve(e)
}, _this.duration)
},
fail: reject,
})
})
}
// 加载弹窗
_runShowLoadingTask() {
return new Promise<void>((resolve) => {
uni.showLoading({ title: `${this.curTask?.title}` })
// eslint-disable-next-line @typescript-eslint/no-this-alias
const _this = this
setTimeout(() => {
// 如果当前执行的任务不是showLoading,那说明已经被 hideloading 关闭,就不需要执行自动关闭
if (this.curTask?.fun !== 'showLoading') {
resolve()
} else {
// 到了指定的时间,自动关闭
this.setCurTask(null)
this.taskList.shift()
resolve()
}
}, _this.curTask?.duration)
})
}
// 关闭showLoading
_runHideLoadingTask() {
// 只有当当前任务为showLoading才执行关闭loading
if (this.curTask?.fun === 'showLoading') {
uni.hideLoading()
this.taskList.shift()
this.setCurTask(null)
this._runTask()
}
}
// 运行任务
_runTask() {
// 队列没有任务退出
if (this.taskList.length === 0) return
// 如果当前有正在执行的任务,不继续执行
if (this.curTask) return
// 取出第一个元素 ,shift 会改变数组长度
this.curTask = this.taskList[0] as TaskItem
const next = () => {
this._runTask()
}
switch (this.curTask.fun) {
case 'toast':
this._runToastTask().then(this.curTask.resolve).catch(this.curTask.reject).finally(next)
break
case 'showLoading':
this._runShowLoadingTask().finally(next)
break
default:
throw new Error('提示任务异常')
}
}
}
// 只允许创建一个Tip实例
const getTipInstance = (() => {
let tip: Tip
return () => {
if (!tip) {
tip = new Tip()
}
return tip
}
})()
const tip = getTipInstance()
/**
* 提示
*/
export const toast = tip.toast.bind(tip)
/**
* 弹窗默认6秒后关闭,也可以传入第二个参数控制显示时间
*/
export const showLoading = tip.showLoading.bind(tip)
/**
* 立即关闭弹窗
*/
export const hideLoading = tip.hideLoading.bind(tip)
2、三个方法
第一个toast是弹窗提示,toast的默认执行时间为1500,返回了一个promise,提示完成后会状态会变成pedding,toast不会覆盖前面的toast,toast不会被hideLoading关闭
第二个showLoading是显示加载,showLoading如果不被hideLoading关闭,到了指定的时间(函数的第二个参数)会被关闭,函数默认的6是秒,showLoading 不会覆盖toast
第三个hideLoading是关闭加载弹框,会立即关闭前面的showLoading,对toast不会有操作
3、测试代码
// 测试弹窗
setTimeout(() => {
toast('你好1')
toast('你好2')
showLoading('加载', 3000) // 加载出现后1.5秒弹窗hideLoading()被关闭,没有hideloading,三秒后关闭
toast('1')
toast('2')
// hideLoading()
}, 1000)
setTimeout(() => {
hideLoading()
}, 4500)