開發一個 React Loading 組件

一直都是用的第三方庫的 loading組件( ant-design/antd-mobil),最近一個項目須要用到loading,本身開發了下,總結以下:

Loading組件的兩種形式:全局loading局部loading

  • 全局loading:通常覆蓋全屏居中顯示,這種狀況的調用方式通常是編程式調用即 Loading.show()
  • 局部loading:只對某個區塊進行loading,這種狀況通常是組件包裹形式使用html

    // 使用方式同 Spin 組件
    <Spin visible={true}>
        <div>區塊內容</div>
    </Spin>
  • 兩種狀況都作了請求速度過快時的防閃爍處理

意外收穫 portals

  • 在開發全局loading時,再想怎麼把loading組件掛在body頂層時,用了 ReactDom.render 這個方法通常是一些腳手架幫咱們生成項目代碼時自動生成的把頂層App組件放在root節點上時纔會用到,這裏咱們利用它把 loading組件掛在咱們指定的頂層dom節點上
  • ReactDOM.createPortal(child, container) 官方文檔知乎學習文章node

    portals這個東西就是能夠把組件放到指定的dom節點,而使用時依舊能夠像普通使用組件那樣使用,只不過生成的dom樹不是在一塊兒的
    
    官方話術:Portal 提供了一種將子節點渲染到存在於父組件之外的 DOM 節點的優秀的方案
    
    並且支持合成事件冒泡
    
    利用這個方法開發一些 Dialog、Modal 組件就很是方便、很是簡潔了
    antd的 Modal 組件也是用的這個方法

兩種loading代碼以下

  • 全局loading,這裏用到了 ReactDom.render(<Comps/>, domNode) 這個頂層apireact

    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');
        }
    }
相關文章
相關標籤/搜索