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

156

У меня есть простое поле ввода в шаблоне Vue, и я хотел бы использовать debounce примерно так:

<input type="text" v-model="filterKey" debounce="500">

Однако это debounceсвойство устарело в Vue 2 . В рекомендации только сказано: «используйте v-on: input + стороннюю функцию debounce».

Как правильно это реализовать?

Я пытался реализовать это с помощью lodash , v-on: input и v-model , но мне интересно, можно ли обойтись без дополнительной переменной.

В шаблоне:

<input type="text" v-on:input="debounceInput" v-model="searchInput">

В скрипте:

data: function () {
  return {
    searchInput: '',
    filterKey: ''
  }
},

methods: {
  debounceInput: _.debounce(function () {
    this.filterKey = this.searchInput;
  }, 500)
}

Затем ключ фильтра используется в computedреквизитах.

МартинТиВарга
источник
1
Попробуйте это stackoverflow.com/questions/41230343/…
sobolevn
3
Я бы посоветовал внимательно прочитать: vuejs.org/v2/guide/…
Марек Урбанович
3
В руководстве есть пример: vuejs.org/v2/guide/computed.html#Watchers
Bengt,

Ответы:

167

Я использую пакет debounce NPM и реализован так:

<input @input="debounceInput">

methods: {
    debounceInput: debounce(function (e) {
      this.$store.dispatch('updateInput', e.target.value)
    }, config.debouncers.default)
}

Используя lodash и пример в вопросе, реализация выглядит так:

<input v-on:input="debounceInput">

methods: {
  debounceInput: _.debounce(function (e) {
    this.filterKey = e.target.value;
  }, 500)
}
Primoz Rome
источник
11
Спасибо за это. Я нашел похожий пример в некоторых других документах Vue: vuejs.org/v2/examples/index.html (редактор
разметки
7
Предлагаемое решение содержит проблему, когда на странице есть несколько экземпляров компонентов. Проблема описана и решение представлено здесь: forum.vuejs.org/t/issues-with-vuejs-component-and-debounce/7224/…
Валера
e.currentTarget перезаписывается таким образом на null
ness-EE
1
Рекомендую добавить v-model=your_input_variableк вводу и в вашем vue data. Таким образом, вы не полагаетесь на e.targetVue, а используете его, чтобы получить доступ this.your_input_variableвместоe.target.value
DominikAngerer
2
Для тех, кто использует ES6, важно подчеркнуть использование здесь анонимной функции: если вы используете стрелочную функцию, вы не сможете получить доступ thisк ней.
Полосон
72

Назначение дебаунда methodsможет быть проблемой. Итак, вместо этого:

// Bad
methods: {
  foo: _.debounce(function(){}, 1000)
}

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

// Good
created () {
  this.foo = _.debounce(function(){}, 1000);
}

Это становится проблемой, если у вас есть несколько экземпляров компонента - аналогично тому, как dataдолжна быть функция, возвращающая объект. Каждому экземпляру нужна своя собственная функция противодействия, если предполагается, что они действуют независимо.

Вот пример проблемы:

Vue.component('counter', {
  template: '<div>{{ i }}</div>',
  data: function(){
    return { i: 0 };
  },
  methods: {
    // DON'T DO THIS
    increment: _.debounce(function(){
      this.i += 1;
    }, 1000)
  }
});


new Vue({
  el: '#app',
  mounted () {
    this.$refs.counter1.increment();
    this.$refs.counter2.increment();
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>

<div id="app">
  <div>Both should change from 0 to 1:</div>
  <counter ref="counter1"></counter>
  <counter ref="counter2"></counter>
</div>

Bendytree
источник
1
Не могли бы вы объяснить, почему назначение debounce в методах может быть проблемой?
MartinTeeVarga
14
См. Примеры ссылок, которые подвержены гниению ссылок. Лучше объяснить проблему в ответе - так будет интереснее для читателей.
MartinTeeVarga
Спасибо, большое совпадение, мне было не по себе, пытаясь понять, почему данные, отображаемые на консоли, были правильными, но не применялись в приложении ...
@ sm4, потому что вместо того, чтобы использовать один и тот же совместно используемый экземпляр debounce для желаемой функции, он воссоздает его каждый раз, тем самым в основном убивая использование debounce.
Майк Шевард 01
2
просто добавьте его в свой data()then.
Су-Ау Хван
62

обновлено в 2020 году

Вариант 1: многоразовый, без депс

(Рекомендуется, если требуется более одного раза в вашем проекте)

helpers.js

export function debounce (fn, delay) {
  var timeoutID = null
  return function () {
    clearTimeout(timeoutID)
    var args = arguments
    var that = this
    timeoutID = setTimeout(function () {
      fn.apply(that, args)
    }, delay)
  }
}

Component.vue

<script>
  import {debounce} from './helpers'

  export default {
    data () {
      return {
        input: '',
        debouncedInput: ''
      }
    },
    watch: {
      input: debounce(function (newVal) {
        this.debouncedInput = newVal
      }, 500)
    }
  }
</script>

Codepen


Вариант 2: внутрикомпонентный, также без зависимостей

(Рекомендуется при однократном использовании или в небольшом проекте)

Component.vue

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

<script>
  export default {
    data: {
      debouncedInput: ''
    },
    computed: {
     input: {
        get() {
          return this.debouncedInput
        },
        set(val) {
          if (this.timeout) clearTimeout(this.timeout)
          this.timeout = setTimeout(() => {
            this.debouncedInput = val
          }, 300)
        }
      }
    }
  }
</script>

Codepen

выкопать
источник
5
ты настоящий герой
Эштониан
5
Я предпочитаю этот вариант, потому что мне, вероятно, не нужен пакет npm для 11 строк кода ....
Бен Виндинг
3
Это должен быть отмеченный ответ, он работает очень хорошо и почти не занимает места. Благодарность!
Александр Клудт,
37

Очень просто без lodash

  handleScroll: function() {
    if (this.timeout) 
      clearTimeout(this.timeout); 

    this.timeout = setTimeout(() => {
      // your action
    }, 200); // delay
  }
pshx
источник
4
Как бы мне ни нравился lodash, это, безусловно, лучший ответ для трейлинг-дребезга. Легче всего реализовать и понять.
Майкл Хейс,
3
также полезно добавить destroyed() { clearInterval(this.timeout) }, чтобы не было тайм-аута после уничтожения.
пикилон
13

У меня была такая же проблема, и вот решение, которое работает без плагинов.

Поскольку <input v-model="xxxx">точно так же, как

<input
   v-bind:value="xxxx"
   v-on:input="xxxx = $event.target.value"
>

(источник)

Я подумал, что могу установить функцию противодействия при назначении xxxx в xxxx = $event.target.value

как это

<input
   v-bind:value="xxxx"
   v-on:input="debounceSearch($event.target.value)"
>

методы:

debounceSearch(val){
  if(search_timeout) clearTimeout(search_timeout);
  var that=this;
  search_timeout = setTimeout(function() {
    that.xxxx = val; 
  }, 400);
},
stallingOne
источник
1
если в вашем поле ввода также было @input="update_something"действие, назовите его послеthat.xxx = val that.update_something();
Neon22,
1
в моем разделе методов я использовал немного другой синтаксис, который у меня сработал:debounceSearch: function(val) { if (this.search_timeout) clearTimeout(this.search_timeout); var that=this; this.search_timeout = setTimeout(function() { that.thread_count = val; that.update_something(); }, 500); },
Neon22
Это нормально, если у вас есть один или очень мало случаев, когда вам нужно отклонить ввод. Однако вы быстро поймете, что вам нужно переместить это в библиотеку или подобное, если приложение будет расти и эта функциональность понадобится где-то еще. Держите код СУХОЙ.
Coreus
5

Обратите внимание, что я опубликовал этот ответ до принятого ответа. Это не так. Это всего лишь шаг вперед от решения вопроса. Я отредактировал принятый вопрос, чтобы показать как реализацию автора, так и окончательную реализацию, которую я использовал.


На основе комментариев и связанного документа по миграции я внес несколько изменений в код:

В шаблоне:

<input type="text" v-on:input="debounceInput" v-model="searchInput">

В скрипте:

watch: {
  searchInput: function () {
    this.debounceInput();
  }
},

И метод, устанавливающий ключ фильтра, остается прежним:

methods: {
  debounceInput: _.debounce(function () {
    this.filterKey = this.searchInput;
  }, 500)
}

Похоже, на один вызов меньше (только на v-model, а не на v-on:input).

МартинТиВарга
источник
Разве это не вызовет debounceInput()дважды для каждого изменения? v-on:обнаружит входные изменения и вызовет debounce, И поскольку модель привязана, функция наблюдения searchInput ТАКЖЕ вызовет debounceInput... верно?
mix3d
@ mix3d Не считайте этот ответ. Это было просто мое расследование, я не хотел задавать этот вопрос. Скорее всего, вы правы. Отметьте принятый ответ. Это правильно, и я отредактировал его, чтобы он соответствовал вопросу.
MartinTeeVarga
Моя ошибка ... Я не понимал, что ты ответил на свой вопрос, ха!
mix3d
5

Если вам нужен очень минималистичный подход к этому, я сделал его (первоначально созданный из vuejs-tips для поддержки IE), который доступен здесь: https://www.npmjs.com/package/v-debounce

Применение:

<input v-model.lazy="term" v-debounce="delay" placeholder="Search for something" />

Затем в вашем компоненте:

<script>
export default {
  name: 'example',
  data () {
    return {
      delay: 1000,
      term: '',
    }
  },
  watch: {
    term () {
      // Do something with search term after it debounced
      console.log(`Search term changed to ${this.term}`)
    }
  },
  directives: {
    debounce
  }
}
</script>
Кореус
источник
Вероятно, это должно быть приемлемое решение, набравшее более 100 голосов. OP запросил подобное компактное решение, и оно прекрасно отделяет логику устранения дребезга.
Барни Сабольч
2

Если вам нужно применить динамическую задержку с функцией lodash debounce:

props: {
  delay: String
},

data: () => ({
  search: null
}),

created () {
     this.valueChanged = debounce(function (event) {
      // Here you have access to `this`
      this.makeAPIrequest(event.target.value)
    }.bind(this), this.delay)

},

methods: {
  makeAPIrequest (newVal) {
    // ...
  }
}

И шаблон:

<template>
  //...

   <input type="text" v-model="search" @input="valueChanged" />

  //...
</template>

ПРИМЕЧАНИЕ: в приведенном выше примере я сделал пример ввода для поиска, который может вызывать API с настраиваемой задержкой, которая предоставляется вprops

Роли Роли
источник
1

Хотя почти все ответы здесь уже верны, если кто-то ищет быстрое решение, у меня есть директива для этого. https://www.npmjs.com/package/vue-lazy-input

Он применяется к @input и v-model, поддерживает настраиваемые компоненты и элементы DOM, debounce и throttle.

Vue.use(VueLazyInput)
  new Vue({
    el: '#app', 
    data() {
      return {
        val: 42
      }
    },
    methods:{
      onLazyInput(e){
        console.log(e.target.value)
      }
    }
  })
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/lodash/lodash.min.js"></script><!-- dependency -->
<script src="https://unpkg.com/vue-lazy-input@latest"></script> 

<div id="app">
  <input type="range" v-model="val" @input="onLazyInput" v-lazy-input /> {{val}}
</div>

undefinederror
источник
0

Если вы используете Vue, вы также можете использовать его v.model.lazyвместо, debounceно помните, что v.model.lazyон не всегда будет работать, поскольку Vue ограничивает его для пользовательских компонентов.

Для пользовательских компонентов вы должны использовать :valueвместе с@change.native

<b-input :value="data" @change.native="data = $event.target.value" ></b-input>

Амир Хадем
источник
0

Если бы вы могли перенести выполнение функции debounce в какой-нибудь метод класса, вы могли бы использовать декоратор из utils-decorators lib ( npm install --save utils-decorators):

import {debounce} from 'utils-decorators';

class SomeService {

  @debounce(500)
  getData(params) {
  }
}
vlio20
источник
-1

Мы можем обойтись с помощью нескольких строк JS-кода:

if(typeof window.LIT !== 'undefined') {
      clearTimeout(window.LIT);
}

window.LIT = setTimeout(() => this.updateTable(), 1000);

Простое решение! Отличная работа! Надеюсь, вам будет полезно, ребята.

танвир993
источник
2
Конечно ... если вы хотите загрязнить глобальное пространство и сделать так, чтобы только 1 элемент мог использовать его одновременно. Это ужасный ответ.
Гибридный веб-разработчик
-1
 public debChannel = debounce((key) => this.remoteMethodChannelName(key), 200)

vue-свойство-декоратор

Mayxxp
источник
2
Не могли бы вы добавить больше информации об этом решении?
rocha
2
Пожалуйста, уточните еще немного. Также обратите внимание, что это старая ветка с хорошо известными ответами, поэтому можете ли вы уточнить, какое решение более подходит для проблемы?
jpnadas,
Будет лучше, если вы объясните, почему это предпочтительное решение, и объясните, как оно работает. Мы хотим обучать, а не просто предоставлять код.
Железный Человек