React在弱網(wǎng)環(huán)境下限制按鈕多次點(diǎn)擊,防止重復(fù)提交問題
React在弱網(wǎng)環(huán)境下限制按鈕多次點(diǎn)擊,防止重復(fù)提交
this.state = { isRepeatClick: true //設(shè)置開關(guān)來控制重復(fù)點(diǎn)擊 } //點(diǎn)擊確定按鈕,防止重復(fù)提交 okHandle = () => { const { isRepeatClick} = this.state if (isRepeatClick) { //如果為true 開始執(zhí)行 this.setState({ isRepeatClick: false }) //將isRepeatClick變成false,將不會(huì)執(zhí)行處理事件 // 在此處編寫點(diǎn)擊事件執(zhí)行的代碼 const that = this // 為定時(shí)器中的setState綁定this setTimeout(function () { // 設(shè)置點(diǎn)擊延遲事件,1秒后將執(zhí)行 that.setState({ isRepeatClick: true }) // 將isRepeatClick設(shè)置為true }, 1000); } };
上述代碼中的setState是異步的,setTimeout也是異步的。
當(dāng)調(diào)用多次okHandle 的時(shí)候,下一次的okHandle會(huì)在上次調(diào)用okHandle 的異步函數(shù)執(zhí)行完成后 再執(zhí)行的setTimeout() 只執(zhí)行 code 一次。
如果要多次調(diào)用,請(qǐng)使用 setInterval() 或者讓 code 自身再次調(diào)用 setTimeout()。
防止提交按鈕重復(fù)點(diǎn)擊的實(shí)踐
防止按鈕重復(fù)點(diǎn)擊
按鈕是前端界面中承接著用戶操作的很重要的一個(gè)環(huán)節(jié),前端界面用戶和系統(tǒng)的交互都通過按鈕來完成,與系統(tǒng)的交互自然就少不了把用戶的意向保存到系統(tǒng)中,如今面對(duì)前后端分離的部署方案,前端與后端的通信都是通過接口來完成的。
那么問題就來了發(fā)送一個(gè)接口就需要等待,那么等待的這段時(shí)間可長可短(根據(jù)用戶當(dāng)前的網(wǎng)絡(luò)時(shí)間決定的),如果一個(gè)請(qǐng)求三秒以后才回來,用戶在這一段時(shí)間再次點(diǎn)擊怎么辦。
在如今這個(gè)網(wǎng)速很快的時(shí)代,可能延遲是非常低的,所以給用戶考慮的時(shí)間并不多,但是如果請(qǐng)求時(shí)間長重復(fù)點(diǎn)擊就需要做限制了。
本次就是基于項(xiàng)目中的發(fā)送請(qǐng)求的按鈕做防止重復(fù)點(diǎn)擊的一些探索。
現(xiàn)在的前端的項(xiàng)目中發(fā)送請(qǐng)求大都采用 async/await 的語法糖吧異步請(qǐng)求封裝,正是因?yàn)槭钱惒秸?qǐng)求,回調(diào)之前的按鈕都是不應(yīng)該點(diǎn)擊的,這樣可以防止一些請(qǐng)求二次發(fā)送造成的一些bug。
舉一個(gè)簡單的例子,在項(xiàng)目中有一個(gè)按鈕是提交按鈕,把用戶的的一些信息通過調(diào)用接口的形式發(fā)送給后端,然后跳轉(zhuǎn)到詳情頁面,這就是一個(gè)很簡單的前端交互的場景。我們來簡單實(shí)現(xiàn)一下(基于React實(shí)現(xiàn)方式)
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('請(qǐng)輸入對(duì)應(yīng)對(duì)容') } copyValue.current = name; resolve('success'); }, 3000) }) const getValue = async () => { try { const flag = await sendValue(value); console.log(flag); console.log(copyValue.current); console.log('其他業(yè)務(wù)操作') } 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"}}>發(fā)送</Button> </> ) }
上面的代碼就是簡單把用戶輸入的內(nèi)容給持久化起來,這里借用了setTimeout來模仿網(wǎng)絡(luò)延遲的效果,如果在請(qǐng)求處理過程中不對(duì)重復(fù)點(diǎn)擊進(jìn)行控制,那么就會(huì)出現(xiàn)下面的情況,連續(xù)發(fā)送好幾次請(qǐng)求,最終效果差強(qiáng)人意。
所以針對(duì)這個(gè)按鈕就需要做重復(fù)點(diǎn)擊控制,每次請(qǐng)求成功的時(shí)候才能恢復(fù)按鈕的可點(diǎn)擊狀態(tài)。
那么簡單的實(shí)現(xiàn)代碼就來了針對(duì)按鈕點(diǎn)擊的方法:
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('其他業(yè)務(wù)操作') } catch (error) { message.warning(error) console.log(error) } }
上面的方式針對(duì)按鈕事件單獨(dú)定義了一個(gè)變量進(jìn)行了控制,每次都是請(qǐng)求完畢的時(shí)候把控制變量給置為false,上面這種方式確實(shí)是可行的但是如果針對(duì)每一個(gè)按鈕事件都需要單獨(dú)定義一個(gè)變量,會(huì)造成內(nèi)部的變量很多后期很難維護(hù)。那么就像節(jié)流一樣能不能針對(duì)這種情況抽離出一個(gè)公共的方法來實(shí)現(xiàn)呢。
經(jīng)過梳理,如果我們抽離出一個(gè)方法,和這種單獨(dú)的寫法是一致的,首先定義一個(gè)變量置為false,然后進(jìn)行第一次請(qǐng)求,這個(gè)時(shí)候給變量置為true,等待請(qǐng)求結(jié)束再給變量置為false,這樣就達(dá)到了控制重復(fù)點(diǎn)擊的效果。
前幾天解除了js的裝飾器,首先想到的就是使用裝飾器在代碼編譯的時(shí)候給注入這一過程??梢詫?duì)于React Hook 是沒有類這一個(gè)概念的。
所以對(duì)于裝飾器也用不了(但是對(duì)于React的class組件還是可以使用的。下面會(huì)給出實(shí)現(xiàn)方式)。
對(duì)于React Hook則可以使用高階函數(shù)的方式實(shí)現(xiàn),傳入一個(gè)方法,返回包裝過的方法,高階函數(shù)類似與下面的方式:
const demo = () => { console.log('處理業(yè)務(wù)邏輯'); } const warpButton = (buttonEvent:Function) => { return () => { console.log('begin'); buttonEvent(); console.log('end'); } } HTML: <Button onClick={warpButton(demo)}>發(fā)送</Button>
經(jīng)過warpButton的包裝可以給注入的方法執(zhí)行一個(gè)額外的邏輯,那么我們實(shí)現(xiàn)的邏輯也就可以基于次來實(shí)現(xiàn)了。下面是代碼:
const getValue = async () => { try { const flag = await sendValue(value) console.log(flag); console.log(copyValue.current); console.log('其他業(yè)務(wù)操作') } 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" }}>發(fā)送</Button>
通過這個(gè)高階函數(shù)可以自動(dòng)幫助我們?cè)趫?zhí)行請(qǐng)求的時(shí)候控制對(duì)應(yīng)的請(qǐng)求狀態(tài),這樣就能夠做到自動(dòng)對(duì)我們注入的函數(shù)進(jìn)行控制。同時(shí)可以根據(jù)傳入的提示信息進(jìn)行提示。
對(duì)于公共方法還需要在考慮一下兼容性,如果這里傳入的就是一個(gè)普通的js方法這樣就報(bào)錯(cuò)了,所以需要對(duì)傳入的方法進(jìn)行判斷,增加兼容性:
代碼如下:
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; } } }
對(duì)與React Hook 中可以使用高階函數(shù)的方式可以實(shí)現(xiàn),對(duì)于之前的class 組件則是可以使用裝飾器了,不僅看上去美觀同時(shí)使用起來也是比較方便。但是裝飾器只能用于類和類的屬性上,不能用于方法上,因?yàn)榇嬖诤瘮?shù)提升。
直接給出裝飾器代碼:
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請(qǐng)求中') 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> </> ) } }
總結(jié)了以上兩種方式不管使用裝飾器或者是高階函數(shù)的方式都可以做到對(duì)按鈕的點(diǎn)擊進(jìn)行控制,但是究其根本還是通過定義變量控制的,所以自己也可以在其他框架中進(jìn)行探索。
在vue中的嘗試:同樣繪制一個(gè)基礎(chǔ)的頁面一個(gè)按鈕一個(gè)輸入框模擬發(fā)送請(qǐng)求:
<template> <div id="app"> <el-input v-model="input" placeholder="請(qǐng)輸入內(nèi)容" 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>
同樣的情況出現(xiàn)了,請(qǐng)求返回之前重復(fù)點(diǎn)擊就會(huì)重復(fù)執(zhí)行。同樣的方法使用高階函數(shù)給他包裝起來。
//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); } }) }
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
使用Node搭建reactSSR服務(wù)端渲染架構(gòu)
這篇文章主要介紹了使用Node搭建reactSSR服務(wù)端渲染架構(gòu),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08React實(shí)現(xiàn)二級(jí)聯(lián)動(dòng)的方法
這篇文章主要為大家詳細(xì)介紹了React實(shí)現(xiàn)二級(jí)聯(lián)動(dòng)的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09關(guān)于react-router中的Prompt組件使用心得
這篇文章主要介紹了關(guān)于react-router中的Prompt組件學(xué)習(xí)心得,Prompt組件作用是,在用戶準(zhǔn)備離開該頁面時(shí),?彈出提示,?返回true或者false,?如果為true,?則離開頁面,?如果為false,?則停留在該頁面,本文結(jié)合示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01React?中的?JS?報(bào)錯(cuò)及容錯(cuò)方案
這篇文章主要為大家介紹了React?中的?JS?報(bào)錯(cuò)及容錯(cuò)方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08axios請(qǐng)求響應(yīng)數(shù)據(jù)加解密封裝實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了axios請(qǐng)求響應(yīng)數(shù)據(jù)加解密封裝實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03React Refs轉(zhuǎn)發(fā)實(shí)現(xiàn)流程詳解
Refs是一個(gè) 獲取 DOM節(jié)點(diǎn)或React元素實(shí)例的工具,在React中Refs 提供了一種方式,允許用戶訪問DOM 節(jié)點(diǎn)或者在render方法中創(chuàng)建的React元素,這篇文章主要給大家介紹了關(guān)于React中refs的一些常見用法,需要的朋友可以參考下2022-12-12