React之防止按钮多次点击事件 重复提交

 更新时间:2023年10月23日 11:01:38   作者:Zeng__Y1  
这篇文章主要介绍了React之防止按钮多次点击事件 重复提交问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

React防止按钮多次点击事件 重复提交

为了方便,简单的记录一下:

在state中设置一个控制点击事件

this.state = {

isClick: true

}

在点击事件的函数里

handleOk = () => {

const { isClick } = this.state

if (isClick) {   //如果为true 开始执行

this.setState({ isClick: false })   //将isClick 变成false,将不会执行处理事件

// 编写点击事件执行的代码

const that = this   // 为定时器中的setState绑定this

setTimeout(function () {       // 设置延迟事件,1秒后将执行

that.setState({ isClick: true })   // 将isClick设置为true

}, 1000);

}

};

防止提交按钮重复点击的实践

防止按钮重复点击

按钮是前端界面中承接着用户操作的很重要的一个环节,前端界面用户和系统的交互都通过按钮来完成,与系统的交互自然就少不了把用户的意向保存到系统中,如今面对前后端分离的部署方案,前端与后端的通信都是通过接口来完成的。

那么问题就来了发送一个接口就需要等待,那么等待的这段时间可长可短(根据用户当前的网络时间决定的),如果一个请求三秒以后才回来,用户在这一段时间再次点击怎么办。

在如今这个网速很快的时代,可能延迟是非常低的,所以给用户考虑的时间并不多,但是如果请求时间长重复点击就需要做限制了。

本次就是基于项目中的发送请求的按钮做防止重复点击的一些探索。

现在的前端的项目中发送请求大都采用 async/await 的语法糖吧异步请求封装,正是因为是异步请求,回调之前的按钮都是不应该点击的,这样可以防止一些请求二次发送造成的一些bug。

举一个简单的例子,在项目中有一个按钮是提交按钮,把用户的的一些信息通过调用接口的形式发送给后端,然后跳转到详情页面,这就是一个很简单的前端交互的场景。我们来简单实现一下(基于React实现方式)

const ButtonClick: React.FC = () => {
    const [value, setValue] = useState('');
    const copyValue = useRef('');
    const sendValue = (name: string): Promise<string> =>
        new Promise((resolve: Function, reject: Function) => {
            setTimeout(() => {
                if (!name) {
                    return reject('请输入对应对容')
                }
                copyValue.current = name;
                resolve('success');
            }, 3000)
        })
    const getValue = async () => {
        try {
            const flag = await sendValue(value);
            console.log(flag);
            console.log(copyValue.current);
            console.log('其他业务操作')
        } catch (error) {
            message.warning(error)
            console.log(error)
        }
    }
    const inputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        setValue(event.target.value)
    }
    return (
        <>
            <Input onChange={inputChange} value={value} style = {{width: "300px"}}></Input>
            <Button onClick={getValue} type = "primary" style = {{marginTop: "20px"}}>发送</Button>
        </>
    )
}

上面的代码就是简单把用户输入的内容给持久化起来,这里借用了setTimeout来模仿网络延迟的效果,如果在请求处理过程中不对重复点击进行控制,那么就会出现下面的情况,连续发送好几次请求,最终效果差强人意。

所以针对这个按钮就需要做重复点击控制,每次请求成功的时候才能恢复按钮的可点击状态。

那么简单的实现代码就来了针对按钮点击的方法:

const [affectLoading,setAffectLoading] = useState(false);
const getValue = async () => {
        if(affectLoading) {
            return;
        }
        setAffectLoading(true);
        try {
            const flag = await sendValue(value).finally(() =>{
                setAffectLoading(false)
            })
            console.log(flag);
            console.log(copyValue.current);
            console.log('其他业务操作')
        } catch (error) {
            message.warning(error)
            console.log(error)
        }
    }

上面的方式针对按钮事件单独定义了一个变量进行了控制,每次都是请求完毕的时候把控制变量给置为false,上面这种方式确实是可行的但是如果针对每一个按钮事件都需要单独定义一个变量,会造成内部的变量很多后期很难维护。那么就像节流一样能不能针对这种情况抽离出一个公共的方法来实现呢。

经过梳理,如果我们抽离出一个方法,和这种单独的写法是一致的,首先定义一个变量置为false,然后进行第一次请求,这个时候给变量置为true,等待请求结束再给变量置为false,这样就达到了控制重复点击的效果。前几天解除了js的装饰器,首先想到的就是使用装饰器在代码编译的时候给注入这一过程。可以对于React Hook 是没有类这一个概念的。所以对于装饰器也用不了(但是对于React的class组件还是可以使用的。

下面会给出实现方式)。对于React Hook则可以使用高阶函数的方式实现,传入一个方法,返回包装过的方法,高阶函数类似与下面的方式:

const demo = () => {
        console.log('处理业务逻辑');
    }
    const warpButton = (buttonEvent:Function) => {
       return () => {
        console.log('begin');
        buttonEvent();
        console.log('end');
       }
    }
    HTML:
    <Button onClick={warpButton(demo)}>发送</Button>

经过warpButton的包装可以给注入的方法执行一个额外的逻辑,那么我们实现的逻辑也就可以基于次来实现了。

下面是代码:

const getValue = async () => {
        try {
            const flag = await sendValue(value)
            console.log(flag);
            console.log(copyValue.current);
            console.log('其他业务操作')
        } catch (error) {
            message.warning(error)
            console.log(error)
        }
    }
    const wrapButton = (buttonEvent: Function, messageValue?: string) => {
        let flag = false;
        return async function () {
            if (flag) {
                messageValue && message.warning(messageValue);
                return;
            }
            flag = true;
            //@ts-ignore
            await buttonEvent.apply(this, arguments).finally(() => {
                flag = false;
            })
        }
    }
    
    HTML:
    <Button onClick={wrapButton(getValue,'loading')} type="primary" style={{ marginTop: "20px" }}>发送</Button>

通过这个高阶函数可以自动帮助我们在执行请求的时候控制对应的请求状态,这样就能够做到自动对我们注入的函数进行控制。同时可以根据传入的提示信息进行提示。

对于公共方法还需要在考虑一下兼容性,如果这里传入的就是一个普通的js方法这样就报错了,所以需要对传入的方法进行判断,增加兼容性:

代码如下

    const wrapButton = (buttonEvent: Function, messageValue?: string) => {
        let flag = false;
        return async function () {
            if (flag) {
                messageValue && message.warning(messageValue);
                return;
            }
            flag = true;
            if (buttonEvent.constructor.name === 'AsyncFunction') {
                //@ts-ignore
                await buttonEvent.apply(this, arguments).finally(() => {
                    flag = false
                })
            } else {
                //@ts-ignore
                buttonEvent.apply(this, arguments);
                flag = false;
            }
        }
    }

对与React Hook 中可以使用高阶函数的方式可以实现,对于之前的class 组件则是可以使用装饰器了,不仅看上去美观同时使用起来也是比较方便。

但是装饰器只能用于类和类的属性上,不能用于方法上,因为存在函数提升。

直接给出装饰器代码:

const lockButton = (value: string = 'loading') => {
    return (target?: any, key?: any, desc?: any) => {
        const fn = desc.value;
        let flag = false;
        desc.value = async function () {
            if (flag) {
                message.warning(value);
                return;
            }
            flag = true;
            console.log(fn.constructor.name === 'AsyncFunction');
            if (fn.constructor.name === 'AsyncFunction') {
                //@ts-ignore
                await fn.apply(this, arguments).finally(() => {
                    flag = false;
                })
            } else {
                fn.apply(this, arguments);
                flag = false;
            }

            return target;
        }
    }
}

在class组件中的使用:

    class ChekcButton1 extends Component<{}, {}> {
    constructor(props: {}) {
        super(props)
        this.state = {

        }
    }
    private getData = (timer: number): Promise<Number> =>
    new Promise((resolve) => {
        setTimeout(() => {
            resolve(timer);
        }, timer)
    })
    @lockButton('异步buttton请求中')
    async getValue1() {
        const value = await this.getData(5000);
        console.log(value);
        if (value > 500) {
            console.log('判断');
        }
    }
    render() {
        return (
            <>
                <div>
                    测试class组件的button装饰器:
                    <div>
                        <Button onClick={this.getValue1.bind(this)} type="primary">测试button</Button>
                    </div>
                </div>
            </>
        )
    }
}

总结了以上两种方式不管使用装饰器或者是高阶函数的方式都可以做到对按钮的点击进行控制,但是究其根本还是通过定义变量控制的,所以自己也可以在其他框架中进行探索。

在vue中的尝试:同样绘制一个基础的页面一个按钮一个输入框模拟发送请求:

<template>
  <div id="app">
    <el-input v-model="input" placeholder="请输入内容" class="inputVDom"></el-input>
    <el-button type="primary" @click="getValue">按钮</el-button>
  </div>
</template>

<script>
import { buttonLock } from "../../util/util";
export default {
  name: "buttonLock",
  data() {
    return {
      input: "",
      copyValue: "",
    };
  },
  methods: {
    getData: function (value) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (!value) {
            this.$message({
              message: "'this needs some value!'",
              type: "warning",
            });
            reject("this needs some Value!");
            return;
          }
          this.copyValue = value;
          resolve("success");
        }, 3000);
      });
    },
    getValue: async function() {
      try {
        const value = await this.getData(this.input);
        console.log(value);
        console.log(this.input)
      }catch(error) {
        console.log(error); 
      }
    },
  },
};
</script>

同样的情况出现了,请求返回之前重复点击就会重复执行。

同样的方法使用高阶函数给他包装起来。

//utils类中方法
const buttonLock = (buttonEvent) => {
    let flag = false;
    console.log(buttonEvent)
    return async function() {
        if(flag) {
            console.log('loading')
            return;
        }
        flag = true;
        await buttonEvent.apply(this,arguments).finally(() => {
            flag = false;
        })
    }
}
//使用:
methods:{
    getValue: buttonLock(async function() {
    try {
        const value = await this.getData(this.input);
        console.log(value);
        console.log(this.input)
      }catch(error) {
        console.log(error); 
      }
})
} 

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • React 正确使用useCallback useMemo的方式

    React 正确使用useCallback useMemo的方式

    这篇文章主要介绍了React 正确使用useCallback useMemo的方式,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下
    2022-08-08
  • React.js入门实例教程之创建hello world 的5种方式

    React.js入门实例教程之创建hello world 的5种方式

    React 是近期非常热门的一个前端开发框架。应用非常广泛,接下来通过本文给大家介绍React.js入门实例教程之创建hello world 的5种方式 ,需要的朋友参考下吧
    2016-05-05
  • 详解三种方式在React中解决绑定this的作用域问题并传参

    详解三种方式在React中解决绑定this的作用域问题并传参

    这篇文章主要介绍了详解三种方式在React中解决绑定this的作用域问题并传参,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • 基于React实现倒计时功能

    基于React实现倒计时功能

    这篇文章主要为大家详细介绍了如何基于React实现倒计时功能,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下
    2024-02-02
  • react-router-dom v6版本实现Tabs路由缓存切换功能

    react-router-dom v6版本实现Tabs路由缓存切换功能

    今天有人问我怎么实现React-Router-dom类似标签页缓存,很久以前用的是react-router v5那个比较容易实现,v6变化挺大,但了解react的机制和react-router的机制就容易了,本文介绍react-router-dom v6版本实现Tabs路由缓存切换,感兴趣的朋友一起看看吧
    2023-10-10
  • React中this丢失的四种解决方法

    React中this丢失的四种解决方法

    这篇文章主要给大家介绍了关于React中this丢失的四种解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者使用React具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-03-03
  • React实现一个倒计时hook组件实战示例

    React实现一个倒计时hook组件实战示例

    这篇文章主要为大家介绍了React实现一个倒计时hook组件,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • React+CSS 实现绘制横向柱状图

    React+CSS 实现绘制横向柱状图

    这篇文章主要介绍了React+CSS 实现绘制横向柱状图,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-09-09
  • 使用Axios在React中请求数据的方法详解

    使用Axios在React中请求数据的方法详解

    这篇文章主要给大家介绍了初学React,如何规范的在react中请求数据,主要介绍了使用axios进行简单的数据获取,加入状态变量,优化交互体验,自定义hook进行数据获取和使用useReducer改造请求,本文主要适合于刚接触React的初学者以及不知道如何规范的在React中获取数据的人
    2023-09-09
  • React和Vue中实现锚点定位功能

    React和Vue中实现锚点定位功能

    在React中,可以使用useState和useEffect钩子来实现锚点定位功能,在Vue中,可以使用指令来实现锚点定位功能,在React和Vue中实现锚点定位功能的方法略有不同,下面我将分别介绍,文中通过代码示例介绍的非常详细,需要的朋友可以参考下
    2024-01-01

最新评论