开发一个 React Loading 组件

javascript/jquery

浏览数:191

2020-7-5

一直都是用的第三方库的
loading组件(
ant-design/antd-mobil),最近一个项目需要用到loading,自己开发了下,总结如下:

Loading组件的两种形式:全局loading局部loading

  • 全局loading:一般覆盖全屏居中显示,这种情况的调用方式一般是编程式调用即 Loading.show()
  • 局部loading:只对某个区块进行loading,这种情况一般是组件包裹形式使用

    // 使用方式同 Spin 组件
    <Spin visible={true}>
        <div>区块内容</div>
    </Spin>
  • 两种情况都做了请求速度过快时的防闪烁处理

意外收获 portals

  • 在开发全局loading时,再想怎么把loading组件挂在body顶层时,用了 ReactDom.render 这个方法一般是一些脚手架帮我们生成项目代码时自动生成的把顶层App组件放在root节点上时才会用到,这里我们利用它把 loading组件挂在我们指定的顶层dom节点上
  • ReactDOM.createPortal(child, container) 官方文档知乎学习文章

    portals这个东西就是可以把组件放到指定的dom节点,而使用时依旧可以像普通使用组件那样使用,只不过生成的dom树不是在一起的
    
    官方话术:Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案
    
    而且支持合成事件冒泡
    
    利用这个方法开发一些 Dialog、Modal 组件就非常方便、非常简洁了
    antd的 Modal 组件也是用的这个方法

两种loading代码如下

  • 全局loading,这里用到了 ReactDom.render(<Comps/>, domNode) 这个顶层api

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './style/loading';
    
    class Loading {
        domNode: HTMLElement
        isExistNode: boolean
        timer: any
        constructor() {
            this.domNode = document.createElement('div');
            this.isExistNode = false;
        }
    
        private render(visible: boolean) {
            if (!this.isExistNode && visible) {
                document.body.appendChild(this.domNode);
                const children = this.createNode();
                ReactDOM.render(children, this.domNode);
                this.isExistNode = true
            }
            if (visible) {
                this.domNode.className = 'hp-loading-wrap';
            } else {
                this.domNode.className = 'hp-loading-wrap hide';
                // ReactDOM.unmountComponentAtNode(this.domNode)
            }
        } 
        createNode() {
            const node = <div className="loading-content"><div className="loading-img"></div></div>;
            return node;
        }
    
        show(isDelay=true, delay=300) {
            this.timer && clearTimeout(this.timer)
            if (!isDelay) {
                this.render(true);
            } else {
                // 防闪烁
                this.timer = setTimeout(() => this.render(true), delay);
            }
        }
    
        hide() {
            this.timer && clearTimeout(this.timer)
            this.render(false)
        }
    }
    
    export default new Loading()
    
    // 样式
    .hp-loading-wrap {
        position: fixed;
        z-index: 99999;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        display: flex;
        align-items: center;
        &.hide {
            display: none;
        }
        .loading-content {
            width: 37.5px;/*no*/
            height: 18px;/*no*/
            margin: 0 auto;
            text-align: center;
        }
        .loading-img {
            width: 100%;
            height: 100%;
            margin: 0 auto;
            background-repeat: no-repeat;
            background-size: contain;
            background-position: center;
            background-image: url(../image/loading.gif)
        }
    }
  • 局部loading,这里用了hooks,使用方式同 antd Spin 组件

    import React, { useState, useEffect } from "react";
    import './style/spin.less'
    
    interface propsType {
        visible: boolean;
        children?: any;
        tips?: any; 
        delay?: number;
    }
    
    let timer:any;
    export default function Spin(props: propsType) {
        const [visible, setVisible] = useState(props.visible);
    
        useEffect(() => {
            if (props.delay) { // 防闪烁
                timer && clearTimeout(timer);
                if (props.visible) {
                    timer = setTimeout(() => setVisible(true), props.delay);
                } else {
                    setVisible(false);
                }
            } else {
                setVisible(props.visible);
            }
        }, [props.visible]);
        return (
            <div className={visible ? "hp-spin" : "hp-spin hide"}>
                {props.children}
                <div className={props.children ? "spin-content" : ''}>
                    <div className="spin-img"></div>
                </div>
            </div>
        );
    }
    
    // 样式
    .hp-spin {
        position: relative;
        .spin-content {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            z-index: 999;
        }
        &.hide .spin-content {
            display: none;
        }
        .spin-img {
            display: inline-block;
            width: 37.5px;/*no*/
            height: 18px;/*no*/
            background-repeat: no-repeat;
            background-size: contain;
            background-image: url('../image/loading.gif');
        }
    }
    

作者:大桔子