Vue公共loading升級版(處理併發非同步差時響應)

波特卡斯D發表於2023-11-20

公共loading是專案系統中很常見的場景,處理方式也不外乎三個步驟:
1.透過全域性狀態管理定義狀態值(vuex、pinia等)。
2.在程式主入口監聽狀態值變化,從而展示/隱藏laoding動畫。
3.在請求和相應攔截器中變更狀態值。

第一二步驟處理大同小異,但在第三步中,網上很多博文分享的方法是:在請求攔截中展示loading,在響應攔截器中判斷收到成功響應時直接隱藏loading,這種方法看似可行但實際過程中卻有問題。
例如,假設在第0秒時同時向後臺傳送了兩個非同步請求A和B,由於網路或處理邏輯不同,A請求0.5秒秒收到成功響應,B請求2秒才收到。那在第0.5秒,響應攔截器就會把loading狀態變更,結束loading動畫,但此時B請求還沒收到返回。如果使用者接下來的操作同時需要A和B請求的資料,提前結束動畫會讓使用者體檢變差。

解決思路:
定義一個全域性物件來儲存每個介面的響應狀態,直到每個請求介面都收到響應才變更狀態,結束loading動畫。因為鍵名的唯一性,可以使用介面路徑(或唯一介面編號)作為鍵名。請求時新增一個鍵值對,響應時變更鍵值,同時遍歷物件狀態值進行判斷

let apiStatusList ={
  '/api/a':true,//true請求中
  '/api/b':false //false請求完成
}

具體操作如下(以vue3的pinia為例):
定義一個loading.js

import { defineStore } from 'pinia';
export const useLoadStore = defineStore('storeLoading', {
  state: () => {
    return {
      apiStatusList:{},
      loading:false, //網路載入狀態,true載入中
    };
  },
  actions: {
    updateLoadingState(value){
      this.loading = value
    },
    setApiStatusList(value){
      this.apiList = value;
    }
  }
});

攔截器處理:

import axios from 'axios';
import { useLoadStore } from '../stores/loading';

const request = axios.create();
//請求攔截
request.interceptors.request.use(
  (config) => {
    //公共loading
    const loadStore = useLoadStore();
    let statusList = { ...loadStore.apiStatusList };
    statusList[config.url] = true; //介面賦值為請求中
    loadStore.setApiStatusList(statusList);
    if (!loadStore.loading) {  //判斷loading是否正在展示中
      loadStore.updateLoadingState(true);
    }

    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
)

//響應攔截
request.interceptors.response.use(
  (response) => {
    const loadStore = useLoadStore();
    let statusList = { ...loadStore.apiStatusList };
    statusList[response.config.url] = false;  ////介面賦值為請求完成
    if (!Object.values(statusList).includes(true)) { //遍歷物件,判斷介面是否全部返回
      if (loadStore.loading) {
        loadStore.updateLoadingState(false);
        loadStore.setApiStatusList({});
      }
    } else {
      loadStore.setApiStatusList(statusList);
    }
  },
  (error) => {//有介面報錯,重置loading
    const loadStore = useLoadStore();
    if (loadStore.loading) {
      loadStore.updateLoadingState(false);
      loadStore.setApiStatusList({});
    }
  }
)

App.vue監聽狀態變化

//監聽store狀態值時需要傳入function
watch(()=>loadStore.loading,(newValue, oldValue)=>{
  if(newValue){
    showLoadingToast({
      duration: 0,
      forbidClick: true,
    });
  }else{
    closeToast();
  }
})

相關文章