React+Antd全局加载遮罩工具

下面是全局加载遮罩工具,功能:提供show和showWithDelay/hide方法用于显示/延时显示/隐藏遮罩,它还提供loading属性返回是否正在loading。通常用于耗时较长的操作,比如远端api调用。

如何用它,下面是个例子,这个是全局的postAction:

javascript 复制代码
import loadingMask from './loadingMask';
...
// 设置延迟显示加载遮罩(1秒后显示)
  loadingMask.showWithDelay('请求处理中,请稍候...', 1000);

  return axios({
    url: url,
    method: 'post',
    data: parameter,
    headers: { ...signHeader, ...config.headers },
    ...config
  }).then(res => {
    // 请求完成后隐藏加载遮罩
    loadingMask.hide();
    return handleResponse(res);
  }).catch(err => {
    // 请求出错后隐藏加载遮罩
    loadingMask.hide();
    return handleError(err);
  });

如果想实时获得它的loading属性呢?这时候要订阅它的状态变化:

javascript 复制代码
const [loading, setLoading] = useState(loadingMask.loading);
// 订阅 loadingMask 的 loading 状态变化
    useEffect(() => {
        // 订阅 loading 状态变化
        const unsubscribe = loadingMask.subscribeToLoading(setLoading);

        // 组件卸载时取消订阅
        return unsubscribe;
    }, []);
    ...
    <Button
                        block
                        type='submit'
                        color='primary'
                        loading={loading}
                    >
                        删除选中记录
                    </Button>

这时候loadingMask的loading状态变化会立即返回到setLoading,也就会引导起button的重新渲染。

组件代码如下:

javascript 复制代码
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Mask, SpinLoading } from 'antd-mobile';
import styled from 'styled-components';

const LoadingContainer = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: #ffffff;
`;

const LoadingText = styled.div`
  margin-top: 12px;
  font-size: 14px;
`;

/**
 * 全局加载遮罩工具
 * 用于在长时间请求时显示加载遮罩
 */
class LoadingMask {
  constructor() {
    this.container = null;
    this.root = null;
    this.visible = false;
    this.timeoutId = null;
    this.loadingText = '正在加载,请稍候...';
    this._loading = false; // 添加内部 loading 状态
    this._listeners = []; // 添加监听器数组
    this.init();
  }

  /**
   * 初始化加载遮罩容器
   */
  init() {
    // 创建容器元素
    this.container = document.createElement('div');
    this.container.id = 'global-loading-mask-container';
    document.body.appendChild(this.container);
    
    // 创建React 18的root
    this.root = createRoot(this.container);
    
    // 初始渲染
    this.render();
  }

  /**
   * 渲染加载遮罩
   */
  render() {
    if (!this.root) {
      this.init();
      return;
    }

    this.root.render(
      <Mask opacity={0.7} visible={this.visible}>
        <LoadingContainer>
          <SpinLoading color='white' style={{ '--size': '48px' }} />
          <LoadingText>{this.loadingText}</LoadingText>
        </LoadingContainer>
      </Mask>
    );
  }

  /**
   * 显示加载遮罩
   * @param {string} text - 加载提示文本
   */
  show(text) {
    this.loadingText = text || '正在加载,请稍候...';
    this.visible = true;
    this._setLoading(true); // 使用新方法设置 loading 状态
    this.render();
  }

  /**
   * 隐藏加载遮罩
   */
  hide() {
    this.visible = false;
    this._setLoading(false); // 使用新方法设置 loading 状态
    this.render();

    // 清除定时器
    if (this.timeoutId) {
      clearTimeout(this.timeoutId);
      this.timeoutId = null;
    }
  }

  /**
   * 设置延迟显示加载遮罩
   * @param {string} text - 加载提示文本
   * @param {number} delay - 延迟时间(毫秒)
   */
  showWithDelay(text, delay = 1000) {
    // 清除之前的定时器
    if (this.timeoutId) {
      clearTimeout(this.timeoutId);
    }
    
    // 立即设置 loading 状态为 true
    this.loadingText = text || '正在加载,请稍候...';
    this._setLoading(true); // 使用新方法设置 loading 状态

    // 设置新的定时器,只延迟显示遮罩
    this.timeoutId = setTimeout(() => {
      this.visible = true;
      this.render();
    }, delay);
  }

  // 添加设置 loading 状态的方法,并通知监听器
  _setLoading(value) {
    if (this._loading !== value) {
      this._loading = value;
      // 通知所有监听器
      this._listeners.forEach(listener => listener(value));
    }
  };

  // 添加订阅方法
  subscribeToLoading(callback) {
    this._listeners.push(callback);
    // 立即通知当前状态
    callback(this._loading);
    
    // 返回取消订阅的函数
    return () => {
      this._listeners = this._listeners.filter(cb => cb !== callback);
    };
  };
}


// 创建单例实例
const loadingMask = new LoadingMask();

// 添加 loading 属性的 getter
Object.defineProperty(loadingMask, 'loading', {
  get: function() {
    return this._loading;
  }
});

export default loadingMask;
相关推荐
Su米苏6 小时前
关于@vue-office/excel 渲染异常滚动失去内容
前端·vue.js·excel
灯澜忆梦6 小时前
【基于GO的Web开发10】gin获取HTML-Form表单提交参数
前端·后端·golang·html·gin
撑伞的鱼99377 小时前
2026年前端AI编程工具评测:Figma 还原、组件复用、跨文件联动三项对比
前端·ai编程·figma·效率工具·ai编程工具
码视野7 小时前
基于 Vue3 + Element Plus 的【微短剧剧本智能创作与分镜生产协同系统】设计与实现(含PRD/三端源码/大屏)
前端·人工智能·vue3
zzzzzz3107 小时前
36K stars 的“酷炫组件”,到底该怎么用才不显得用力过猛?
前端·react.js·动效
2401_8949155310 小时前
GEO 优化源码全解析:从搜索引擎到 AI 引擎的底层改写逻辑
java·服务器·前端·数据库·人工智能·分布式·搜索引擎
rockey62715 小时前
C#脚本引擎之AScript与Jurassic、Jint对比
javascript·c#·.net·js·script·动态脚本
Profile排查笔记16 小时前
指纹浏览器哪个好?从 Profile、代理、权限和自动化能力判断是否适合
前端·人工智能·后端·自动化
lzhdim16 小时前
12、JavaScript常见的内存泄露问题 - JavaScript学习系列文章
开发语言·前端·javascript·学习·ecmascript
denggun1234516 小时前
yield
前端·数据库·python