JavaScript – 实现一个虚拟进度的Class

前言

在我们日常的开发中,有时候会遇到一些业务功能,需要让用户做一个等待的动作,且无法获取真实的一个等待时间,这时候我们就会有需求是为了提升用户的交互体验感而去模拟一个真实的进度条,让用户会一直等待下去。

  • 知道了需要去模拟一个真实的进度条,那我们就开始对这个虚拟进度的功能进行一个简单的分析:
  1. 接受虚拟进度的默认时间范围。
  2. 开始加载、结束加载的触发函数。
  3. 加载状态的回调,例如加载是否OK,当前百分比更新。
  4. 动态的setTimeout执行时间。
  5. 是否加载完成( bl )。
  6. 假进度总时间。
  7. 假进度总时间的剩余折半时间。
  8. 当前的一个进度计算。
  9. 不跟任何框架技术( Vue,React )做关联,但是能给任意框架快速使用。

开始搭架子

第一步:接受的参数类型定义:这里我们接受的默认时间以及2个外界可以被用到的回调函数。

interface FakeLoadingParamsType {
    initTime?: number
    setLoading?: (_bl: boolean) => void
    setSchedule?: (_num: number) => void
}
   
复制代码

第二步:搭建我们这个Class的基本结构和声明动作行为

 


interface FakeLoadingParamsType {
    initTime?: number
    setLoading?: (_bl: boolean) => void
    setSchedule?: (_num: number) => void
}

class FakeLoading {
    private initTime = 5; // 初始化时间 ( 真实时间为 initTime - initTime * 2  )
    private isOK = false; // 是否ok
    private timeOut = 0; // 循环时间
    private num = 0; // 假进度时间
    private half = 0; // 折半的剩余进度
    private count = 0; // 进行进度
    private _setLoading(_bl: boolean) { } // 回调 - 当前是否处于加载中
    private _setSchedule(_num?: number) { } // 回调 - 当前进度百分比
    constructor(props: FakeLoadingParamsType) {
        // 1. 接受外界定义的世界 - 覆盖默认时间
        // 2. 接受外界传进来的回调函数到我们内部函数
    }
    /**
     * 开始动作
     */
    public begin = () => {
        //  1. 初始化数据
        //  2. 触发回调
        //  3. 循环函数入口
    }
    /**
     * 结束动作
     */
    public over = () => {
        // 1. 强制更新状态、以及触发回调
    }
    /**
    * 计算增加进度的速度(翻倍)
    */
    private computedSpeed() {
    }
    /**
     * 自调用延时增加进度
     */
    private circulationSubCount() {
        // 1. setTimeout 根据定义的时间自调用
        // 2. 计算当前进度 - 每超过一半就改变触发时间 computedSpeed
        // 3. 判断是否到达临界值(99),强行指正不在递增
        // 4. 计算当前进度百分比值
        // 5. 自调用
    }
}
复制代码

第三步: 接受参数并初始化 – 代码

   constructor(props?: FakeLoadingParamsType) {
        // 1. 接受外界定义的世界 - 覆盖默认时间
        this.initTime = props?.initTime || this.initTime;
        // 2. 接受外界传进来的回调函数到我们内部函数
        this._setLoading = (bl: boolean) => {
            props?.setLoading?.(bl);
        };
        this._setSchedule = (num: number) => {
            props?.setSchedule?.(num);
        };
    }
复制代码

第四步: 开始和结束的动作 – 代码

    public begin = () => {
        //  1. 初始化数据
        this.num = parseInt(this.initTime * Math.random() + "") + this.initTime;
        this.half = this.num / 2;
        this.count = 0;
        this.timeOut = 200;
        this.isOK = false;
        //  2. 触发回调
        this._setSchedule(0);
        this._setLoading(true);
        //  3. 循环函数入口
        this.circulationSubCount();
    }
复制代码
 public over = () => {
        // 1. 强制更新状态、以及触发回调
        this.isOK = true;
        this._setSchedule(100);
        setTimeout(() => {
            this._setLoading(false);
        }, 200);
    }
复制代码

第五步: 自调用循环函数的实现 – 代码

 private circulationSubCount() {
        // 1. setTimeout 根据定义的时间自调用
        setTimeout(() => {
            if (this.isOK) {
                return;
            }
            // 2. 计算当前进度 - 每超过一半就改变触发时间 computedSpeed
            this.computedSpeed();
            this.count += 0.1;
            // 3. 判断是否到达临界值(99),强行指正不在递增
            if (this.count >= this.num) {
                this._setSchedule(99);
            } else {
                // 4. 计算当前进度百分比值
                this._setSchedule((this.count / this.num) * 100);
            }
            // 5. 自调用

            this.circulationSubCount();
        }, this.timeOut);
    }
复制代码

第六步:计算增加进度的速度(翻倍) – 代码

这一步其实就是简单的模拟加载进度条越来越慢的一个导向,例如:

总进度是百分之100,我现在加载到百分之50,当前我的加载状态已经超过我总时间的一半,那我就把setTimeout的回调时间翻一倍,并且重新去计算剩下的一半时间(依次从50、25、12.5…)。

    private computedSpeed() {
        if (this.count >= this.half) {
            this.timeOut = this.timeOut * 2;
            this.half += this.half / 2;
        }
    }
复制代码

尝试使用:

function setSchedule(num: number) {
    console.log(num);
}
function setLoading(bl: boolean) {
    console.log(bl);
}
const fakeLoading = new FakeLoading({
    setSchedule, setLoading
})
fakeLoading.begin()
复制代码

输出:

我们会发现已经开始按照预期的设定去执行代码了,并且在99%的时候不在递增。

image.png

image.png

业务使用 – 采用React作为举例:

在自定义hook中使用。

  1. 可接受生命周期的回调
  2. 可更新UI
  3. 暴露出当前进度(schedule)和加载状态(loading)
  • 自定义hook实现 – demo (这是一个最简单的例子):
import { useEffect, useRef, useState } from "react";
import FakeLoading from "./FakeLoading";
export const useLoading = (params) => {
    const { beginCallBack, overCallBack } = params || {};
    const [schedule, setSchedule] = useState<number>(0);
    const [loading, setLoading] = useState(false);
    const LoadingObj = useRef({});
    useEffect(() => {
        LoadingObj.current = new FakeLoading({
            setSchedule,
            setLoading,
        });
 
    }, []);
    return {
        begin: () => {
            beginCallBack?.();
            LoadingObj.current?.begin?.();
        },
        over: () => {
            LoadingObj.current?.over?.();
            overCallBack?.();
        },
        loading,
        setLoading,
        schedule: parseInt(schedule + ""),
    };
};
复制代码
  • UI层 使用自定义hook – demo:
      const { begin, over, schedule, loading } = useLoading({
         // 虚拟进度class的生命周期、可不传
        beginCallBack: () => {
            // 业务代码
        },
        overCallBack: () => {
           // 业务代码
        },
    });
复制代码
  • 在其他地方使用,例如:Redux、dva、Mobx、Vue-X使用是同等道理的。

完整的代码:

export interface FakeLoadingParamsType {
    initTime?: number;
    setLoading?: (_bl: boolean) => void;
    setSchedule?: (_num: number) => void;
}

export class FakeLoading {
    private initTime = 5; // 初始化时间 ( 真实时间为 initTime - initTime * 2  )
    private isOK = false; // 是否ok
    private timeOut = 0; // 循环时间
    private num = 0; // 假进度时间
    private half = 0; // 折半的剩余进度
    private count = 0; // 进行进度
    private _setLoading(_bl: boolean) { } // 回调 - 当前是否处于加载中
    private _setSchedule(_num?: number) { } // 回调 - 当前进度百分比
    constructor(props?: FakeLoadingParamsType) {
        // 1. 接受外界定义的世界 - 覆盖默认时间
        this.initTime = props?.initTime || this.initTime;
        // 2. 接受外界传进来的回调函数到我们内部函数
        this._setLoading = (bl: boolean) => {
            props?.setLoading?.(bl);
        };
        this._setSchedule = (num: number) => {
            props?.setSchedule?.(num);
        };
    }
    /**
     * 开始动作
     */
    public begin = () => {
        //  1. 初始化数据
        this.num = parseInt(this.initTime * Math.random() + "") + this.initTime;
        this.half = this.num / 2;
        this.count = 0;
        this.timeOut = 200;
        this.isOK = false;
        //  2. 触发回调
        this._setSchedule(0);
        this._setLoading(true);
        //  3. 循环函数入口
        this.circulationSubCount();
    }
    /**
     * 结束动作
     */
    public over = () => {
        // 1. 强制更新状态、以及触发回调
        this.isOK = true;
        this._setSchedule(100);
        setTimeout(() => {
            this._setLoading(false);
        }, 200);
    }
    /**
    * 计算增加进度的速度(翻倍)
    */
    private computedSpeed() {
        if (this.count >= this.half) {
            this.timeOut = this.timeOut * 2;
            this.half += this.half / 2;
        }
    }
    /**
     * 自调用延时增加进度
     */
    private circulationSubCount() {
        // 1. setTimeout 根据定义的时间自调用
        setTimeout(() => {
            if (this.isOK) {
                return;
            }
            // 2. 计算当前进度 - 每超过一半就改变触发时间 computedSpeed
            this.computedSpeed();
            this.count += 0.1;
            // 3. 判断是否到达临界值(99),强行指正不在递增
            if (this.count >= this.num) {
                this._setSchedule(99);
            } else {
                // 4. 计算当前进度百分比值
                this._setSchedule((this.count / this.num) * 100);
            }
            // 5. 自调用
            this.circulationSubCount();
        }, this.timeOut);
    }
}

复制代码

总结

我们通过最简单的一个class就实现了一个虚拟进度的功能,其实往深度扩展远不止如此,可以做到高自定义的暂停、重新加载、真假混合。有兴趣的小伙伴们可以自行探索哟!

最后,如果本文章对你有帮助的话,欢迎点歌赞?。感谢!!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享