全局统一添加loading

1,777 阅读3分钟

怎样统一添加loading

背景

页面加载普遍需要一个加载中的过程,简单直接的方案就是在每个页面单独添加,但是确实很冗余。所以能不能统一添加呢?

思路

loading的过程说白了就是接口请求的过程,所以大体思路就是我们可以在请求拦截器中做处理,请求时loading,结束时取消loading,完美~

干货 - 直接上核心代码(此处用vue3 + Element Plus举例说明)

1. store

import { reactive } from 'vue';
import { defineStore } from 'pinia';

type pageDataType = {
    requestingObj?: any;
    route?: any;
};

// page store
export const usePage = defineStore('page', () => {
    // 请求数据
    const pageData = reactive<pageDataType>({
        requestingObj: {}, // 正在请求的接口集合,若存在正在请求的接口,则loading
        route: {}, // 路由信息,用于获取当前页面加载loading的dom,即meta.loadingContainerDom
    });

    /**
     * 更新请求数据
     * @param newData { pageDataType }
     */
    const updatePageData = (newData: pageDataType) => {
        if (newData.requestingObj)
            pageData.requestingObj = newData.requestingObj;
        if (newData.route) pageData.route = newData.route;
    };
    return { pageData, updatePageData };
});

2. axios

拦截器:

import { ElLoading } from 'element-plus';
import { usePage } from '@/stores/page';
import type {
    CreateAxiosDefaults,
    AxiosInstance,
    AxiosRequestConfig,
} from 'axios';
type AxiosRequestType = AxiosRequestConfig & {
    loading?: boolean; // 加载页面loading
};

// 显示loading
let loadingInstance: any = null;
let loadingTimer: number = 0;
const showLoading = (target: string = 'body') => {
    // 加一个延迟,若加载时间小于100ms,则显示loading,算是一个小优化,也可以不要延迟
    loadingTimer = window.setTimeout(() => {
        if (loadingTimer && !loadingInstance) {
            loadingInstance = ElLoading.service({
                target,
                background: '#ccc',
            });
        }
    }, 100);
};
// 关闭loading
const closeLoading = () => {
    loadingTimer && window.clearTimeout(loadingTimer);
    loadingTimer = 0;
    loadingInstance && loadingInstance.close();
    loadingInstance = null;
};

// 请求拦截
axios.interceptors.request.use((config: AxiosRequestType) => {
    const { pageData, updatePageData } = usePage();
    const { requestingObj, route } = pageData;
    // 判断如果是需要loading的请求,则在requestingObj中添加此数据
    if (config.loading) {
        const key = config.method + (config.url || ''); // 这里的key命名方式可能不太严谨,有可能不唯一,但是为了后边为了方便删除此项,所以暂且先如此命令
        requestingObj[key] = true;
        updatePageData({ requestingObj });
    }
    // 如果有需要loading的请求,则loading
    if (JSON.stringify(requestingObj) !== '{}') {
        showLoading(route.meta.loadingContainerDom);
    }

    return config;
});

// 响应拦截
axios.interceptors.response.use(
    (res: any) => {
        const { config } = res;
        const { pageData, updatePageData } = usePage();
        const { requestingObj }: any = pageData;
        const key = config.method + config.url;
        // 如果需要loading的请求数为空,则不再loading
        if (config.loading && requestingObj[key]) {
            delete requestingObj[key];
            updatePageData({ requestingObj });
        }

        if (JSON.stringify(requestingObj) === '{}') {
            closeLoading();
        }
    },
);

api

axios.get('/report/info', {
    loading: true,
})

3. 路由

拦截器

import { usePage } from '../stores/page';

router.afterEach((to, from, failure) => {
    // 切换页面之后清空此页面的loading,另外更新路由信息 - 为了获取route.meta.loadingContainerDom
    const { updatePageData } = usePage();
    updatePageData({ requestingObj: {}, route: to });
    return true;
});

路由配置

...
// 在meta标签中添加loadingContainerDom
{
    name: 'report',
    path: 'report',
    component: ReportList,
    meta: {
        loadingContainerDom: '.base-layout_main',
    },
},
...

思路/解释

  1. 首先是全局状态管理store:

    1.1 因为页面加载不一定只有一个接口,所以要把需要loading的请求全局保存一下 - requestingObj

    1.2 loading是个遮罩,如果直接加在整个页面上,我想切换页面的话前提是这个页面的loading结束才能切换,体验效果差,故loading加在哪里?需要指定dom,所以我们可以通过在路由配置中添加此dom,因此就有了另一个变量 - route

    这里为什么不直接在使用route的地方(axios拦截器)直接用useRoute方法直接获取呢?具体方法如下:

    import { useRoute } from 'vue-router';
    ​
    const route = useRoute();
    

    这不就有了么?!

    因为useRoute必须在setup()中使用,所以这里用store中转了一下(官网说明),store的存储是在router.afterEach中处理的。

  2. 然后是axios:

    2.1 拦截器的逻辑就和我们最开始说的思路是一致的,请求拦截中将若存在需要loading的请求,则显示loading,另外需要将其存一下,因为有可能,很有可能不止一个请求,所以需要在请求结束后(响应拦截)将其删掉,直到所有请求都结束才取消loading

    2.2 api中将需要loading的请求添加loading字段

  3. 最后是路由:

    3.1 拦截器中,在页面切换后取消所有loading,此时也需要更新route信息,为了获取loading的容器route.meta.loadingContainerDom

    3.2 路由配置中在meta标签中添加容器loadingContainerDom

完~