#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>