Как реализовать debounce в vue3

#vue.js #vuejs3 #debounce

#vue.js #vuejs3 #debounce

Вопрос:

У меня есть поле ввода фильтра, и я хочу отфильтровать список элементов. Список большой, поэтому я хочу использовать debounce для задержки применения фильтра до тех пор, пока пользователь не перестанет печатать, для улучшения пользовательского опыта. Это мое поле ввода, и оно привязано к filterText, который используется для фильтрации списка.

 <input type="text" v-model="state.filterText" />
 

Ответ №1:

Привет, впервые отвечаю на что-то здесь, поэтому исправьте мой ответ столько, сколько захотите, я был бы признателен. Я думаю, что самое красивое и легкое решение — создать директиву глобально, которую вы можете использовать столько, сколько захотите, во всех ваших формах.

сначала вы создаете файл с помощью своей директивы, например. debouncer.js

и вы создаете функцию для деблокирования

     //debouncer.js
    /*
      This is the typical debouncer function that receives
      the "callback" and the time it will wait to emit the event
    */
    function debouncer (fn, delay) {
        var timeoutID = null
        return function () {
          clearTimeout(timeoutID)
          var args = arguments
          var that = this
          timeoutID = setTimeout(function () {
            fn.apply(that, args)
          }, delay)
        }
      }

    /*
      this function receives the element where the directive
      will be set in and also the value set in it
      if the value has changed then it will rebind the event
      it has a default timeout of 500 milliseconds
    */
    module.exports = function debounce(el, binding) {
      if(binding.value !== binding.oldValue) {
        el.oninput = debouncer(function(){
          el.dispatchEvent(new Event('change'))
        }, parseInt(binding.value) || 500)
      }
    }
 

После определения этого файла вы можете перейти к своему main.js импортируйте его и используйте экспортированную функцию.

     //main.js
    import { createApp } from 'vue'
    import debounce from './directives/debounce' // file being imported
    
    const app = createApp(App)

    //defining the directive
    app.directive('debounce', (el,binding) => debounce(el,binding))

    app.mount('#app')
 

И это сделано, когда вы хотите использовать директиву для ввода, вы просто делаете это так, без импорта или чего-либо еще.

     //Component.vue
    <input
       :placeholder="filter by name"
       v-model.lazy="filter.value" v-debounce="400"
    />
 

V-модель.ленивая директива важна, если вы решите сделать это таким образом, потому что по умолчанию она обновит ваше связанное свойство при событии ввода, но установка этого параметра заставит его вместо этого ожидать события изменения, которое является событием, которое мы излучаем в нашей функции debounce . Выполнение этого остановит обновление v-model до тех пор, пока вы не прекратите запись или не истечет время ожидания (которое вы можете установить в значении директивы).
Надеюсь, это было понятно.

Комментарии:

1. Хороший подход. Я пытался использовать его, но есть проблема — когда я выдаю update:model-value из компонента vue, $event это просто новое значение, а не объект события js, как в случае с собственными html-компонентами. Поэтому, когда я слушаю @change , я получаю событие js, и у меня нет доступа к новому значению. Есть идеи, как это решить? Я попытался добавить debouncer(function(inputEvent){....}) , но, похоже, это не событие, исходящее от компонента. Я думаю el.oninput , что его нужно заменить эквивалентностью @update:model-value , а не прослушивателем событий dom, но я не знаю, как это сделать

Ответ №2:

Я не нашел никакого хорошего решения, так как хотел увидеть свою привязку в своем шаблоне, поэтому я решил поделиться своим решением. Я написал простую функцию debounce и использую следующий синтаксис для привязки поведения:

 setup() {
...

  function createDebounce() {
    let timeout = null;
    return function (fnc, delayMs) {
      clearTimeout(timeout);
      timeout = setTimeout(() => {
        fnc();
      }, delayMs || 500);
    };
  }

  return {
    state,
    debounce: createDebounce(),
  };
},
 

И синтаксис шаблона:

     <input
      type="text"
      :value="state.filterText"
      @input="debounce(() => { state.filterText = $event.target.value })"
    />
 

Комментарии:

1. Я бы вернул отмененный метод, setup так что шаблон просто делает @input="debouncedFilter" это. В разделе Настройка верните debouncedFilter: createDebounce((evt)=>{state.filterText=evt.target.value})

Ответ №3:

 <template>
    <input type="text" :value="name" @input="test" />
    <span>{{ name }}</span>
</template>
 
 <script lang="ts">
import { defineComponent, ref } from 'vue'
function debounce<T> (fn: T, wait: number) {
  let timer: ReturnType<typeof setTimeout>
  return (event: Event) => {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      if (typeof fn === 'function') {
        fn(event)
      }
    }, wait)
  }
}

export default defineComponent({
  setup () {
    const name = ref('test')
    function setInputValue (event: Event) {
      const target = event.target as HTMLInputElement
      name.value = target.value
    }
    const test = debounce(setInputValue, 1000)
    return { name, test }
  }
})
</script>
 

Ответ №4:

С Lodash у вас есть более простое решение:

 <template>
    <input type="text" :value="name" @input="onInput" />
    <span>{{ name }}</span>
</template>

<script>
import debounce from "lodash/debounce"
export default {
  setup () {
    const onInput = debounce(() => {
      console.log('debug')
    }, 500)
    return { onInput }
  }
}
</script>
 

Ответ №5:

 <input @input="updateValue"/>

const updateValue = (event) => {
  const timeoutId = window.setTimeout(() => {}, 0);
  for (let id = timeoutId; id >= 0; id -= 1) {
    window.clearTimeout(id);
  }

  setTimeout(() => {
    console.log(event.target.value)
  }, 500);
}; 

Вы можете попробовать это

Ответ №6:

Вот пример с синтаксисом Lodash и script setup, использующим наблюдатель для запуска отмененного вызова api:

 <script setup>
import { ref, watch } from 'vue'
import debounce from 'lodash.debounce'

const searchTerms = ref('')

const getFilteredResults = async () => {
  try {
    console.log('filter changed')
    // make axios call here using searchTerms.value
  } catch (err) {
    throw new Error(`Problem filtering results: ${err}.`)
  }
}

const debouncedFilter = debounce(getFilteredResults, 250) // 250ms delay

watch(() => searchTerms.value, debouncedFilter)    
</script>

<template>
    <input v-model="searchTerms" />
</template>