前端頁面中的儲存邏輯
今天一位朋友在群裡問到一個問題「app 中設定介面修改設定的時候,每修改一項,就會觸發 loading,導致使用者體驗不佳,如何避免?」
這是一個非常常見的「編輯 - 儲存」頁面,一般來說,這類頁面的邏輯分成兩種:一類是單獨有個儲存按鈕進行儲存;另一類是修改一項生效一項,無需額外儲存。
單獨按鈕儲存,常見於後臺管理系統、或者是移動端的資料編輯頁面上。以 Form 為單位,一次把所有內容提交到後臺。單獨設定儲存按鈕,我們可以在儲存邏輯執行前通過彈窗,讓使用者對操作進行確認,通過點選「儲存」或者「取消」,來讓使用者決定是否執行儲存。試想,對於一個編輯使用者頁面,如果走的是立即生效的邏輯,很可能我們無意間的一些隨意的修改,就把資料改掉了,比如無意間刪除了使用者名稱字中的一個字,除非我們記得這個字是什麼然後手動改回來,我們是無法「撤銷」我們的操作的。另外,對於一些依賴於其他輸入項的表單驗證行為,我們也需要當用戶修改完所有專案後,統一進行驗證和提交伺服器,例如修改密碼頁面。
而像手機的設定頁面上,當我們設定螢幕亮度的時候,我們希望立即看到效果,而不是點選儲存按鈕,才能看到亮度變化了。我們也不希望修改這些非常基礎的設定,系統還要我們確認是否確定我們的操作。因此對於希望能夠即時看到修改效果,並且無需額外確認的頁面,我們可以講請求後臺的邏輯放到onChange,或者onLostFocus的時候。
回到最初的問題,朋友說的「每修改一項,就會觸發loading」的情況,就是使用了無需儲存按鈕的方法,來設計的頁面。
和手機上儲存設定不同,app 裡面的一些設定,是儲存在伺服器上的,每次改變設定,都需要去發起 API 呼叫,如果沒有 loading, 當我們頻繁的去切換設定,app 會連續傳送多個請求到後臺,給伺服器造成壓力,並且對於 Http 請求來說,如果沒有設定 keep-alive,每次請求都會建立新的 TCP 連線,可能我們 app 上快速操作傳送的請求是 開 關 開,到了伺服器端的順序變成了 開 開 關,導致設定與我們的想法相違背。通過加入 loading,實際是人為加了限制,必須在前一次 API 呼叫結束後,才可以發起下一次呼叫,減少伺服器壓力並防止頻繁請求導致異常情況,loading 的虛擬碼如下:
if (!loading) {
loading = true;
await queryBackend(args);
loading = false;
}
很多表單頁面,也會在提交的時候,將 submit 按鈕給禁用,避免重複點選造成資料異常。
但對於立即生效式的表單,頻繁的 loading 就像每修改一項就彈出 confirm 一樣的讓人反感,在開發中,我們可以通過函式的節流(throttle)和防抖(debounce),來實現邏輯的優化,總的來說,節流和防抖都是在時間軸上控制執行的次數。
節流(throttle)
讓一個函式無法在很短的時間間隔內連續呼叫,當上一次函式執行後過了規定的時間間隔,才能進行下一次該函式的呼叫。
節流邏輯的虛擬碼如下
function throttle(method, time){
var timer = null;
var startTime = new Date();
return function(){
var context = this;
var endTime = new Date();
var resTime = endTime - startTime;
//判斷大於等於我們給的時間採取執行函式;
if(resTime >= time){
method.call(context);
//執行完函式之後重置初始時間,等於最後一次觸發的時間
startTime = endTime;
}
}
}
防抖(debounce)
讓一個函式在一定間隔內沒有被呼叫時,才開始執行被呼叫方法。
防抖邏輯的虛擬碼付下
function debounce(method,time){
var timer = null ;
return function(){
var context = this;
//在函式執行的時候先清除timer定時器;
clearTimeout(timer);
timer = setTimeout(function(){
method.call(context);
},time);
}
}
具體是該用防抖還是節流,要看具體的場景,假如說我們需要在頁面上調節一盞燈光的亮度,通過節流,我們可以在邊調節邊看到燈光明暗的變化,如果使用了防抖,這意味著只有當我們停止了調節,燈光才會產生變化。假如說我們調節的是一個溫度旋鈕,比如把空調溫度從 22° 調節到 26°,我們不需要將 23°、24°、25°這些中間的狀態傳送給伺服器,只需要最終的設定,這種時候防抖就更適合了。