如何在Vue3中实现防抖函数(debounce)

7

我有一个筛选输入框,想要对一组项目进行筛选。由于列表内容较多,因此我想使用防抖技术来延迟应用筛选,以改善用户体验。这是我的输入框,它绑定到filterText上,用于对列表进行筛选。

<input type="text" v-model="state.filterText" />
8个回答

15

我没有找到理想的解决方案,因为我想在模板中看到我的绑定,所以我决定分享我的解决方案。我编写了一个简单的防抖函数,并使用以下语法来绑定这个行为:

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 })"
    />

我会从 setup 返回防抖方法,这样模板只需使用 @input="debouncedFilter"。在 setup 中,返回 debouncedFilter: createDebounce((evt)=>{state.filterText=evt.target.value}) - danbars
1
在Vue3中是否没有内置方式,就像我们在alpinejs中有的input @input.debounce - anjanesh

13

嗨,我第一次在这里回答问题,请尽情纠正我的答案,感激不尽。

我认为最漂亮、最轻量级的解决方案是创建一个全局指令,在您所有的表单中都可以随意使用。

首先,您需要创建带有指令的文件,例如 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-model.lazy指令非常重要,因为默认情况下它会在输入事件上更新绑定属性,但设置此项将使其等待更改事件,而这是我们在防抖函数中发出的事件。这样做将阻止v-model自行更新,直到您停止编写或超时时间结束(可以在指令的值中设置)。


2
不错的方法。我尝试使用它,但是有一个问题——当我从vue组件中发出update:model-value时,$event只是新值,而不像原生html组件一样是js事件对象。因此,当我监听@change时,我得到了js事件,但我无法访问新值。有什么解决办法吗?我尝试添加debouncer(function(inputEvent){....}),但似乎并不是从组件发出的事件。我认为需要用@update:model-value的等效物替换el.oninput,而不是dom的事件监听器,但我不知道该怎么做。 - danbars

11

对于一些更简单的解决方案,您可以使用流行的库:

  1. 使用 Lodash
<template>
  <input type="text" v-model="searchText" @input="onInput" />
</template>

<script setup>
import debounce from "lodash/debounce"
const onInput = debounce(() => {
  console.log(searchText.value)
}, 500)
</script>

使用 vueUse
<template>
  <input type="text" v-model="searchText" @input="onInput" />
</template>

<script setup>
import { useDebounceFn } from "@vueuse/core"
const onInput = useDebounceFn(() => {
  console.log(searchText.value)
}, 500)
</script>

3
这是一个使用Lodash和脚本设置语法的示例,使用观察器来触发防抖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>

3

<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);
};

You can try this one


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

感谢分享您的解决方案,但仅有代码的答案并没有真正帮助。请尝试通过对您所做的事情进行评论来改进您的答案。 - honk31

1

1
创建一个防抖的 ref,只有在最新的 set 调用之后经过一定的超时时间后才会更新该值:
import { customRef } from 'vue'

export function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

组件中的使用方法:

<script setup>
import { useDebouncedRef } from './debouncedRef'
const text = useDebouncedRef('hello')
</script>

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

https://vuejs.org/api/reactivity-advanced.html#customref


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接