開發一個 React Loading 元件

大桔子1223發表於2020-03-08

一直都是用的第三方庫的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');
        }
    }
    
    複製程式碼

相關文章