防止用户重复点击某个按钮的一个思路

生效场景

1.我们在做网游的时候经常会有这样子的一个场景。玩家点击“领取金币”按钮。此时,需要同后端进行数据上的交互。等待后端给你答复之后。才会弹出“”恭喜你领取了100金币“之类的对话框。同时,领取按钮会自动消失。不会让玩家再次领取。
2.要实现这个功能,我们当然要确保玩家在第一次点击领取金币按钮到服务器返回数据的这个时间段内。不要重复点击这个“领取金币”的按钮。大部分的做法是在全场景上挂一个转圈。转圈存在的时候,吞噬掉所有的触摸。
3.但是我接触过的很多游戏,比如说《皇室战争》却不是这样子的,我开宝箱的时候,网络经常卡主,但是我还可以玩命的点击这个打开宝箱。只不过后边的这些个点击,按钮有缩放的反馈,但是却不真正的发送网络请求。
4.笔者最近观摩了TS语法内的注解。想到了使用注解来拦截函数并且实现上述功能的目的。特此记录分享给大家。

简单的例子

  1. 假设我们场景有一个按钮,点击可以获取金币。但是要等待服务器返回。我们可以这样子写
  //领取按钮绑定了这个 onButtonClick方法
  public onButtonClick() {
        console.log('我要去获取金币了');

        setTimeout(() => {
            //模拟网络请求回来需要五秒中
            this.onCoinResp();
        }, 5000);
    }

   //网络请求回来后获得金币
   public  onCoinResp() {
        console.log('恭喜你获得了100金币');
    }

2.现在我们希望在第一次点击按钮并且发出网络请求之后立刻锁住 onButtonClick这个函数,必须等到onCoinResp被触发后,onButtonClick才会被解锁。为此,我们需要写一些注解(对于注解不了解的可以去查阅一下TS的文档)。注解的核心代码 FunLockUtils.ts如下。

interface PropertyDescriptor {
    configurable?: boolean;
    enumerable?: boolean;
    value?: any;
    writable?: boolean;
    get?(): any;
    set?(v: any): void;
}

interface FunItem {
    target: any,
    fun: any
};

let funList: Array<FunItem> = [];

export function lockable() {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        let oldValue = descriptor.value;
        descriptor.value = function (...args: any) {
            for (let item of funList) {
                if (item.target == this && item.fun == descriptor.value) {
                    log(propertyKey + '已经被锁定了,无法执行');
                    return;
                }
            }

            oldValue.call(this, ...args);
        }
    }
}

export function lockFunction(target: any, fun: any): boolean {
    for (let item of funList) {
        if (item.target == target && item.fun == fun) {
            return false;
        }
    }

    funList.push({ target: target, fun: fun });
    return true;
}

export function unlockFunction(target: any, fun: any): boolean {
    for (let i = 0; i < funList.length; i++) {
        let item = funList[i];
        if (item.target == target && item.fun == fun) {
            funList.splice(i, 1);
            return true;
        }
    }
    return true;
}

3.接着改造我们的刚才写上函数

  //这里为onButtonClick 加上lockable注解,表示这个函数是可以被锁上的
   @lockable()
    public onButtonClick() {
        console.log('我要去获取金币了');

        //在发送网络请求后立刻锁上onButtonClick这个函数
        lockFunction(this, this.onButtonClick);

        setTimeout(() => {
            //模拟网络请求回来的样子
            this.onCoinResp();
        }, 5000);
    }

    onCoinResp() {
        console.log('恭喜你获得了100金币');
       //这网络协议返回后才会解锁onButtonClick这个函数
        unlockFunction(this, this.onButtonClick);
    }

4.接着运行我们的项目,并且狂点击“获取金币”这个按钮。可以看到控制台的输出如下
`Q7}J%EJE)2VUP8~G%T}PX

5.至此,大功告成。

4赞

其实有可能就是重复发请求了,但是他们服务器记录了每一个领奖位置是否已领,所以不会造成重复领取

额,刚才帖子没有写完哈。现在写完了。这里,笔者介绍了一个防止重复发送请求的方法。

如果这个界面有多个按钮,点击一个按钮时候, 要锁住所有按钮,这样写起来就不太友好了

相信我,这种界面不会很多的。不是每个按钮都要做网络交互的

按钮点击回调,封装一个通用的0.5秒响应一次即可

最好的设计是从网络接口设计,每个发送消息都可以设置对应收到回复包的回调。这样写起逻辑思路就清晰了。实现方法我采取的方法是每个包都有一个sessionID服务器回复消息也待会这个ID客户端发消息按ID保存回调收到ID消息触发回调。这样在写逻辑的时候就简单了。你这个需求按钮回调先判断标记。有标记就直接跳过,没标记就注册标记,发送消息在消息回复接口清除标记就行了。

这种在后端有个专业的术语:幂等

另外这种功能一般不用button来做锁的粒度,一般用业务作为粒度,这样做:lock(业务key),unlock(业务key),if(isLock(业务key))

这样写感觉也不是很好和加个屏蔽层其实差不多,我觉得像皇室那样是客户端先跑逻辑,后端验证后返回客户端再修正不然总是做不到那么流畅的

做的很不错了。

我是直接在网络请求那边封装了个方法,要等请求结果出来,才让用户继续点击按钮,这样会比0.5秒好一点

网络层做验证好点

如果消息堵塞或者没收到消息,还得处理解锁.

3赞

涉及到异步效果都需要锁定操作,比如网络、动画等,你这种只处理了单点逻辑, 如果是涉及到界面开启、关闭的动画,那么就需要屏蔽整个界面内部的按钮响应,可以将按钮分组,直接根据实际的情况屏蔽分组按钮的响应

防止用户重复点击的好像web前端有防抖与节流

这种注解优化开发我们封装了很多…你这个我们加了时间或者指定一个函数,还有事件网络数据回调,监控函数,名字文本自动切割长度,数值文本格式化加逗号,标注声音资源转化为编辑器里的播放这个音频函数等等…

Mark mark