# Loading

集中处理项目中的loading 问题。

# 核心思想

劫持request,每次调接口都会往 loadingQueue 中 push 一个当前 loading的id,每次接口调用结束,删除对应的id,如果 loadingQueue 有值 就保持loading 展示,如果 loadingQueue 为空数组,卸载loading组件。

import ReactDOM from 'react-dom';
import PageLoading from '@/components/PageLoading';

const TIMEOUT_TIME = 1000 * 60; // 超时时间 60s

const loadingMount = () => {
  ReactDOM.render(
    <PageLoading />,
    document.querySelector('.fullScreenLoading'),
  );
};

const loadingUnmount = () => {
  ReactDOM.unmountComponentAtNode(document.querySelector('.fullScreenLoading') as Element);
};

const handleFullScreenLoading = () => {
  let loadingQueue: any[] = [];
  let timePlan: any = null;

  const timeOutStart = () =>
    setTimeout(() => {
      // eslint-disable-next-line no-use-before-define
      clearAll();
      clearTimeout(timePlan);
    }, TIMEOUT_TIME);

  const setLoadingRootEle = () => {
    const attr = document.createAttribute('class');
    attr.value = 'fullScreenLoading';

    const node = document.createElement('div');
    node.setAttributeNode(attr);
    setTimeout(() => {
      // that umi render content component maybe replace all root children element, so append child async
      document?.querySelector('#root')?.appendChild(node);
    }, 0);
  };

  const renderLoading = () => {
    if (loadingQueue.length === 1) {
      timePlan = timeOutStart();
      loadingMount();// 单例模式
    } else if (!loadingQueue.length) {
      clearTimeout(timePlan);
      loadingUnmount();
    }
  };

  const showLoading = (loadingId: number | string) => {
    loadingQueue.push({ id: loadingId });
    renderLoading();
  };

  const closeLoading = (loadingId: number | string) => {
    if (loadingQueue.length) {
      loadingQueue = loadingQueue.filter((item) => item.id !== loadingId);
    }

    renderLoading();
  };

  const clearAll = () => {
    loadingQueue = [];
    renderLoading();
  };

  setLoadingRootEle();

  return (loadingId: number | string) => {
    if (loadingId) {
      showLoading(loadingId);
    }
    return {
      renderLoading,
      showLoading,
      closeLoading,
      clearAll,
    };
  };
};

export default handleFullScreenLoading();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

# 应用

import { request } from 'umi';
import handleLoading from '../utils/handleFullScreenLoading';
import handleWithLoginTimeout from '../utils/handleWithLoginTimeout';

type Method = 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';

const resetInterval = handleWithLoginTimeout();

const calcParams = (param: { method: Method; [key: string]: any }) => {
  const { method } = param || {};
  const key = (method === 'GET' && 'params') || 'data';
  const { withoutLoading, resetTokenTime = true, ...restParams } = param[key] || {};

  const loadingId = (!withoutLoading && `request_loading_${Date.now().toString(36)}`) || '';

  return {
    params: {
      ...param,
      [key]: restParams,
    },
    loadingId,
    resetTokenTime,
  };
};

const requestWithLoading = <Res>(url: string, originParam: { method: Method; [key: string]: any }) => {
  const { params, loadingId, resetTokenTime } = calcParams(originParam);
  // loading start;
  const { closeLoading } = handleLoading(loadingId);

  return (
    request<{
      success: boolean;
      code: string;
      ext: string;
      msg: string;
      result: Res;
    }>(url, params)
      .then((res) => {
        resetInterval(resetTokenTime);
        return Promise.resolve(res);
      })
      // eslint-disable-next-line arrow-body-style
      .catch((error) => {
        return Promise.resolve({ success: false, code: '', msg: error, result: null });
      })
      .finally(() => {
        // loading end;
        closeLoading(loadingId);
      })
  );
};

export default requestWithLoading;

export function Request<T, Res>(url: string, method: Method = 'GET') {
  return (params?: T, options?: Record<string, unknown>) => {
    const key = (method === 'GET' && 'params') || 'data';

    return requestWithLoading<Res>(url, {
      method,
      [key]: params,
      ...options,
    });
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66