前情
uni-app是我比較喜歡的跨平臺框架,它能開發小程序/H5/APP(安卓/iOS),重要的是對前端開發友好,自帶的IDE可視化的運行和打包也讓開發體驗也非常棒,公司項目就是主推uni-app,為了用戶體驗對于耗時操作,如接口請求或者異步的API調用都會添加loading效果
原生API使用持性
uni-app項目本身自帶有原生交互反饋的API,如showToast、showLoading、showModal、showActionSheet,各有應用場景,而其中showLoading就是用于顯示一個加載中效果的,同時跟他配套的還有hideLoading,用于隱藏已經顯示的loading效果
showLoading特性:永遠只會有一個,如果同時調用多次只會顯示最后調用的那一個
hideLoading特性:調用它會關掉頁面上的正在顯示的loading
思考:
- 因特性引起的一些使用問題
假設一個場景,我頁面上同時調起二個接口,一個接口都會調起一個loading ,最后顯示的是后調接口的loading,但是此時前一個接口回來了,會調用hideLoading接口,就會隱藏loading,其實第二個接口還沒有回來,導致loading被關了,測試代碼如下:
const test0 = () => {uni.showLoading({title: '加載中...0',mask: true});setTimeout(() => {uni.hideLoading();}, 1000);
}const test1 = () => {uni.showLoading({title: '加載中...1',mask: true});setTimeout(() => {uni.hideLoading();}, 2000);
}
test0();
test1();
- 使用體驗問題
在我們做接口請求的時候,接口速度是會受用戶的網速影響,網速好就接口返回快,網絡差就接口返回慢,這里會有一個問題,如果用戶網絡好那loading會閃一下就沒了,用戶都沒看到是什么東西,在一定層面上給用戶帶來不好的體驗
解決方案
- 解決因特性引起的使用問題
因hideLoading并不會識別當前顯示是由誰喚起的loading,導致無法識別當前要隱藏的是哪一個Loading,那我們就封裝下代碼,記錄已經喚起的loading,在調用loading的時候通過傳參指定要隱藏的是哪一個 loading就行了
- 優化體驗問題
我們無法確認用戶的網絡狀態,那我們在喚起loading的時候是否可以做一個延時顯示,假設我們延時為300ms,如果發現300ms內有喚起當前loading的hideloading調用,那當前loading也就不用顯示了,但是這又有一個問題,如果網絡正好回復時間了310,那一樣是閃一下,所以我們再加一個loading的最小顯示時間
uni-app項目一想到要全局操作,首選第一想到就是事件通信了,此處我們基于uni-app自帶的事件通信 o n 、 on、 on、emit來實現一個能解決上面缺陷的loading顯示與隱藏方案,完整代碼如下:
// 用于存儲記錄當前已有loading
let loadingObj = {};
// loading需要顯示的基準時間
let loadingDelayShow = 300;
// loading顯示的最小時間
let loadingDelayHide = 1000;
let loadingTimer = {};// 默認 loading 配置
const loadingOptionsDefault = {mask: true
}/*** 初始化loading* 基于事件通知實現 loading 的顯示與隱藏*/
export const initLoading = (options = '數據加載中...') => {// 監聽顯示 loading 事件uni.$off('showLoading');// optionsIn是loading的配置,參考showLoading的配置,// 其中key用于存儲當前是什么哪一個loading,用于與hideLoading配合使用uni.$on('showLoading', (optionsIn = options, key) => {console.log('---- showLoading ----:', optionsIn, key);// 如果傳入的是字符串,則將其作為 titleconst loadingOptions = typeof optionsIn === 'string' ? { ...loadingOptionsDefault, title: optionsIn }: { ...loadingOptionsDefault, ...optionsIn };if (!loadingObj[key]) {loadingObj[key] = {show: false,startTime: Date.now()};}// 如果300ms內又調用了hideloading,則無需顯示loadingloadingTimer[key] = setTimeout(() => {if (loadingObj[key] && !loadingObj[key].show) {loadingObj[key].show = true;loadingObj[key].startTime = Date.now();uni.showLoading({...loadingOptions});}}, loadingDelayShow);});// 監聽隱藏 loading 事件uni.$off('hideLoading');uni.$on('hideLoading', (key) => {if (loadingObj[key]) {loadingObj[key].show = false;clearTimeout(loadingTimer[key]);}if (isCanHide(key)) {const { startTime } = loadingObj[key];if (Date.now() - startTime >= loadingDelayHide) {resetLoading();} else {setTimeout(() => {if (isCanHide(key)) {resetLoading();}}, loadingDelayHide - (Date.now() - startTime));}}});// 監聽重置 loading 事件uni.$off('resetLoading');uni.$on('resetLoading', () => {try {resetLoading();} catch (err){console.log('---- resetLoading ----', err);}});
}// 判斷要不要隱藏loading
const isCanHide = (key) => {if (!loadingObj[key].show && Object.keys(loadingObj).length === 1) {return true;}// 或者所有的loading對象的show都是falsefor (const key in loadingObj) {if (loadingObj[key].show) {return false;}}return true;
}const resetLoading = () => {loadingObj = {}uni.hideLoading();
}// 導出事件名稱常量
export const LOADING_EVENTS = {SHOW: 'showLoading',HIDE: 'hideLoading',RESET: 'resetLoading'
};// 導出默認對象,包含事件名稱常量
export default {LOADING_EVENTS
};
使用方式:
在項目根目錄main.js中初始化
import { initLoading } from '@/utils/loading';
initLoading()
在需要的地方調用
// 顯示loading
uni.$emit('showLoading', '提交訂單中...', 'xxx');// 隱藏loading
uni.$emit('hideLoading', 'xxx');
使用注意事項
- 其中xxx是key,要確保唯一,實在是怕重復的話可以引入第三方的uuid來做
- 顯示與隱藏要配套使用,不然記錄的loading不會清空或者狀態不會變,會導致問題
小結
這是我的 uni-app項目的loading使用方案,已使用在生產中,還沒有發現什么問題,如果后續發現什么問題再做更新吧,解決方案千千萬,世上沒有最好,只有更好,如果你有更好的解決方案,可以分享出來,或者你發現此方案有什么問題,也可以提出來一起探討。