当前位置: 代码迷 >> 综合 >> hook 写一个Loading案例
  详细解决方案

hook 写一个Loading案例

热度:85   发布时间:2023-11-21 18:34:23.0

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

对于一些加载比较慢的资源,组件最初展示标准的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 ==> useStateconst [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,而不是loadingPropNameconst isDelayed = useDelayHint(props.isLoading, 2000);return <PureDisplay {
    ...props} isDelayed={
    isDelayed} />;
};

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

  相关解决方案