hook 写一个Loading案例

原文地址
我们在实际的业务场景下,遇到一个需求:

对于一些加载比较慢的资源,组件最初展示标准的Loading效果,但在一定时间(比如2秒)后,变为“资源较大,正在积极加载,请稍候”这样的友好提示,资源加载完毕后再展示具体内容。

对于一个展示的组件来说,我们希望的逻辑就是这样的:

const PureDisplay = ({isLoading, isDelayed, data}) => {
    if (isDelayed) {
        return <div>'Please wait a little more...'</div>;
    }

    if (isLoading) {
        return <div>'Loading...'</div>;
    }

    return <div>{data}</div>;
};

通过isDelayed和isLoading这2个属性来表达3种状态(初始加载中、加载用时过长、已经加载完毕),使用条件分支展示不同的内容。

在以往,我们很容易判断出来isDelayed的获取可以通过HOC来实现,因此我们也判断它可以用Hook来实现复用。但是在面对如何实现这个Hook的时候,出现了不小的疑惑,甚至无从下手。

最后在一翻讨论后,我们发现一种方法,即先实现一个HOC版本,再“翻译”成对应的Hook,能快速完成对应的代码。

HOC版

假设我们需要实现一个withDelayHint的HOC来实现这一逻辑,简单整理了一下它的功能:

  • 知道组件当前是否在loading状态,如果不在的话就不用开定时器了。
  • 如果处在loading状态,则打开一个定时器,指定时间后将isDelayed由false改为true。
  • 如果loading状态发生了变化,则需要停掉定时器,并回到第1步重新判断是不是要开新的定时,用于组件状态更新的场合。

基于上面的整理,HOC需要至少2个参数:

  • 如何获取loading状态。最简单地方法是提供一个属性的名称,直接从props[loadingPropName]拿,函数化一些可以提供一个函数来通过getLoading(props)获取。
  • 定时器的延迟时长,以毫秒为单位。
    因此,它的实现还是相对简单的:
import React from 'react'
const pureDisplay=({isLoading,isDelady,data})=>{
    if(isDelady){
        return <div>'Please wait a little more ...'</div>
    }
    if(isLoading){
        return <div>'Loading'</div>
    }
    return <div>数据:{data}</div>
}
const withDelayHint=(loadingName,delay)=>ComponentIn=>{
    const ComponentOut=class extends React.Component{
        state={
            timer:null,
            isDelady:false
        }
        tryStartTimer=()=>{
            this.setState({isDelady:false})
            //处于loading状态时 则开启定时器
            if(this.props[loadingName]){    
                const timer=setTimeout(()=>{
                    this.setState({isDelady:true})
                },delay)
                this.setState({timer})
            }
        }
        componentDidMount(){
            this.tryStartTimer()
        }
        componentDidUpdate(prevProps){
            //如果数据回来后 则取消定时器
            if(prevProps[loadingName]!==this.props[loadingName]){
                clearTimeout(this.state.timer);
                this.tryStartTimer()                
            }
        }
        componentWillUnmount(){
            clearTimeout(this.state.timer);
        }
        render(){
            const {isDelady}=this.state;
            console.log('props',this.props);
            return <ComponentIn {...this.props} isDelady={isDelady}></ComponentIn>
        }
    }
    return ComponentOut;
}
//在使用上,将组件用HOC包装一次,即可以拿到isDelayed属性:
const DisplayWithDelay=withDelayHint('isLoading',2000)(pureDisplay);
export default DisplayWithDelay
//使用
<DisplayWithDelay isLoading={true} />

Hook版

在React官方提供的hook中,与组件中各种逻辑都有对应的版本,比如:

  • setState对应useState。
  • 生命周期对应useEffect。
    因此,我们把上面的代码一一通过映射来实现。需要注意的是,因为hook本身并不是组件的实现,所以是获取不到props的,因此hook不会有“从props中获取isLoading”这个逻辑,而是直接接收isLoading的值就行:
import React, { useRef, useEffect, useState } from 'react'
const PureDisplay = ({ isLoading, isDelady, data }) => {
    if (isDelady) {
        return <div>'Please wait a little more ...'</div>
    }
    if (isLoading) {
        return <div>'Loading'</div>
    }
    return <div>数据:{data}</div>
}
const useDelayHint = (loading, delay) => {
    //和 render无关的属性可以用 useRef 保存
    const timer = useRef(null);
    // setState ==> useState
    const [delayed, setDelayed] = useState(false);
    //生命周期
    useEffect(
        () => {
           const timer=loading?setTimeout(()=>setDelayed(true),delay):setDelayed(false);
            //清除逻辑
            return ()=>clearTimeout(timer);
        },
        //componentDidUpdate
        [loading]
    );
    return delayed;
}

使用

const HookDisplay = props => {
    // 这里直接给isLoading,而不是loadingPropName
    const isDelayed = useDelayHint(props.isLoading, 2000);

    return <PureDisplay {...props} isDelayed={isDelayed} />;
};

可以看到,原本用于HOC的PureDisplay组件在此处还能继续用,这让HOC迁移到Hook的成本非常的小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值