Vue 練習:關鍵字即時查詢之去抖動(Debounce)處理
0 |
我寫了一個在 <input type="text" v-model="keywd" >
輸入關鍵字搜尋顏色的 Vue 3 範例:
<div id="app">
<h2>HTML Color Names</h2>
<div>
<input type="text" v-model="keywd" placeholder="Keyword">
<img src="magnify.png" alt="Search" width="24" height="24">
</div>
<div class="list">
<div v-for="color in filteredColors" class="color-tag" :style="autoColor(color)">{{color.hex}} {{color.name}}</div>
</div>
</div>
<script>
const app = Vue.createApp({
data() {
return {
colors: colors,
keywd: ''
}
},
methods: {
autoColor(color) {
const r = parseInt(color.hex.substring(1, 3), 16);
const g = parseInt(color.hex.substring(3, 5), 16);
const b = parseInt(color.hex.substring(5, 7), 16);
const complementary = (r * 0.299 + g * 0.587 + b * 0.114) > 186 ? '#000000' : '#ffffff';
return {
'background-color': color.hex,
'color': complementary
}
}
},
computed: {
filteredColors() {
const keywd = this.keywd.trim().toLowerCase();
return this.colors.filter(color => {
return color.name.toLowerCase().includes(keywd) ||
color.hex.toLowerCase().includes(keywd);
})
}
}
});
app.directive('debounce', debounceDirective);
app.mount('#app');
</script>
程式運作正常,每敲一個字元都會觸發 keywd 欄位更新,用關鍵字重新篩選顏色產生清單。但是依據我的設計經驗,以字元為單位觸發查詢並非良好設計。例如:使用者想查詢 black,在打字過程將觸發多餘的 b、bl 查詢,一來浪費運算資源(若為 AJAX 呼叫則還有頻寬成本且會增加伺服器負擔),二則輸入過程畫面會快速閃動,冒出非期望範圍的 "B"rown、"Bl"ue... 操作體驗不佳。遇到這類問題,可透過 Debounce (去抖動)技巧改善。(延伸閱讀:打造更貼心的連動欄位網頁)
Debounce 在 JavaScript 用 setTimeout + clearTimeout 可輕鬆實現,但這個案例需跟 Vue v-model 整合,該怎麼做?
爬文找到幾種不同做法,其中我覺得最簡潔的做法是設定 v-model.lazy 改用 change 事件啟動,再自訂 v-debounce Directive 攔截 input 事件搭配 setTimeout 觸發 change 事件,順便可練習我從沒寫過的 自訂 Directive。平日常用的 v-model、v-bind、v-on 是系統內建的 Directive,而自訂 Directive 的概念是挑選 created、beforeMount、mounted、beforeUpdate、updated、beforeUnmount、unmounted 等事件加入邏輯,透過 el、binding、vnode、prevNode 等輸入參數存取 DOM 元素及 Directive 設定。
以 <input v-debounce="300">
為例,el 會傳入 INPUT 元素,binding.value 為 300,因此可以 INPUT addEventListener('input', ...) 攔截輸入過程的內容異動,搭配 setTimeout 技巧實現 Debounce,輸入停止 300ms 時 el.dispatchEvent(new Event('change')) 觸發 change 事件。
直接看範例:
<div id="app">
<h2>HTML Color Names</h2>
<div>
<input type="text" v-model.lazy="keywd" v-debounce="300" placeholder="Keyword">
<img src="magnify.png" alt="Search" width="24" height="24">
</div>
<div class="list">
<div v-for="color in filteredColors" class="color-tag" :style="autoColor(color)">{{color.hex}} {{color.name}}</div>
</div>
</div>
<script>
var debounceDirective = {
beforeMount(el, binding) {
let hnd;
el.addEventListener('input', () => {
clearTimeout(hnd);
hnd = setTimeout(() => el.dispatchEvent(new Event('change')), binding.value || 500);
});
}
};
</script>
<script>
const app = Vue.createApp({
data() {
return {
colors: colors,
keywd: ''
}
},
methods: {
autoColor(color) {
const r = parseInt(color.hex.substring(1, 3), 16);
const g = parseInt(color.hex.substring(3, 5), 16);
const b = parseInt(color.hex.substring(5, 7), 16);
const complementary = (r * 0.299 + g * 0.587 + b * 0.114) > 186 ? '#000000' : '#ffffff';
return {
'background-color': color.hex,
'color': complementary
}
}
},
computed: {
filteredColors() {
const keywd = this.keywd.trim().toLowerCase();
return this.colors.filter(color => {
return color.name.toLowerCase().includes(keywd) ||
color.hex.toLowerCase().includes(keywd);
})
}
}
});
app.directive('debounce', debounceDirective);
app.mount('#app');
</script>
實測如下,連續輸入 bla,將直接顯示最終查詢結果。嗯,感覺好多了。線上展示
又多學會一些 Vue 小技巧。
Exmaple of adding debounce on v-model in Vue3.
Comments
Be the first to post a comment