我寫了一個在 <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

Post a comment