很多時候我們點選按鈕來提交資料,但是在網路條件不好或者互動提示不明確的情況下,使用者會在段時間內多次點選按鈕,如果沒有對按鈕做保護就會造成重複的資料提交,造成資料異常,今天就分享一個比較通用的解決方案。
解決這個問題的思路就是增加一個變數來維護現有按鈕的狀態,但是一個頁面裡如果有很多按鈕,那麼就要申明同等數量的變數,這對維護來說很不友好。
##現有問題
<div class="button">
提交
</div>
複製程式碼
var button = document.querySelector('.button');
button.onclick = submit;
function submit (e) {
// 模擬非同步
var promiseCb = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('提交成功');
}, 1000);
})
return promiseCb.then(
function (res) {
// 處理回撥
console.log(res);
}
)
}
複製程式碼
解決方法
為了更好的封裝舊的程式碼,就必須避免增加額外的變數,所以寫了一個 actionDelegate 函式來對原有的 action 進行封裝,而原有的程式碼只需要修改一下就可以了
button.onclick = submit;
↓
button.onclick = actionDelegate(submit);
function actionDelegate (action) {
// do something
}
複製程式碼
然後我們要對 action 進行型別的判斷,如果 action 返回的是普通的物件,那麼我們認為這個 action 是一個同步的函式;如果 action 返回的是一個 promsie,那麼我們認為我們需要等待這個 promise 狀態結束後才能讓這個 action 再次執行
function actionDelegate (action) {
// 獲取函式返回值
var returnValue = action(e);
// 判斷返回值是 promise
if (returnValue && returnValue.constructor &&
returnValue.constructor.name === 'Promise') {
// 保護按鈕不被狂點
}
else {
// let it go
}
}
複製程式碼
所以在 submit 函式中最終必須要返回一個 promise 就變得很重要
function submit (e) {
// 模擬非同步
var promiseCb = new Promise(...)
return promiseCb
}
複製程式碼
接著我們就要處理最重要的部分了,那就是儲存按鈕的狀態,在這個案例裡我通過 event 獲取到了按鈕的 node,並且把狀態儲存在 node 的 attr 上,這邊也可以使用其他的方式去儲存狀態
function actionDelegate (action) {
return function (e) {
if (e.target.getAttribute('progress-status') === 'processing') {
// 如果按鈕上有處理中的狀態則跳過後續邏輯
return false;
}
// 獲取函式返回值
var returnValue = action(e);
// 判斷返回值是 promise
if (returnValue && returnValue.constructor &&
returnValue.constructor.name === 'Promise') {
// 關鍵點 把按鈕狀態儲存在 node 的屬性上
e.target.setAttribute('progress-status', 'processing')
return returnValue.then(
function () {
// promise 結束後重置狀態
e.target.setAttribute('progress-status', 'initial');
}
)
}
}
}
複製程式碼
最後程式碼組裝起來就是下面的樣子
var button = document.querySelector('.button');
button.onclick = actionDelegate(submit);
function submit (e) {
// 模擬非同步
var promiseCb = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('提交成功');
}, 1000);
})
return promiseCb.then(
function (res) {
// 處理回撥
console.log(res);
}
)
}
function actionDelegate (action) {
return function (e) {
if (e.target.getAttribute('progress-status') === 'processing') {
// 如果按鈕上有處理中的狀態則跳過後續邏輯
return false;
}
// 獲取函式返回值
var returnValue = action(e);
// 判斷返回值是 promise
if (returnValue && returnValue.constructor &&
returnValue.constructor.name === 'Promise') {
var originInnerHTML = e.target.innerHTML;
// 關鍵點 把按鈕狀態儲存在 node 的屬性上
e.target.setAttribute('progress-status', 'processing')
e.target.innerHTML = '提交中...';
return returnValue.then(
function () {
// promise 結束後重置狀態
e.target.setAttribute('progress-status', 'initial');
e.target.innerHTML = originInnerHTML;
}
)
}
}
}
複製程式碼
後記
本來是用 angularjs 實現的一個指令,用來代替 ng-click 的,後來發現在別的專案裡也要用,所以就用原生的程式碼重新實現了邏輯。在 angularjs 中可能會更好實現,因為指令本身會有獨立的作用域就不需要重複申明變數了。今天在寫教程的時候突然發現用 attr 來實現可能更方便,各位如果有更好的實現方式可以來交流。
謝謝
如果喜歡這篇文章 可以關注專欄 也請點贊分享哦
##JSbin