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

У меня есть простое поле ввода в шаблоне 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 реквизитах.


person MartinTeeVarga    schedule 13.02.2017    source источник
comment
Попробуйте вот это stackoverflow.com/questions/41230343/   -  person sobolevn    schedule 13.02.2017
comment
Я предлагаю внимательно прочитать: vuejs .org / v2 / guide /   -  person Marek Urbanowicz    schedule 13.02.2017
comment
В руководстве есть пример: vuejs.org/v2/guide/computed.html# Наблюдатели   -  person Bengt    schedule 13.02.2017


Ответы (14)


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

<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)
}
person Primoz Rome    schedule 13.02.2017
comment
Спасибо за это. Я нашел аналогичный пример в некоторых других документах Vue: vuejs.org/v2/examples/index.html (редактор уценки) - person MartinTeeVarga; 14.02.2017
comment
Предлагаемое решение содержит проблему, когда на странице есть несколько экземпляров компонентов. Проблема описана и решение представлено здесь: форум. vuejs.org/t/issues-with-vuejs-component-and-debounce/7224/ - person Valera; 25.01.2018
comment
e.currentTarget перезаписывается таким образом на null - person ness-EE; 12.09.2018
comment
Спасибо, вы мне очень помогли с моим компонентом vue.js ... - person Matt Komarnicki; 08.01.2019
comment
Рекомендую добавить v-model=your_input_variable во вход и в ваш vue data. Таким образом, вы не полагаетесь на e.target, а используете Vue, чтобы получить доступ к this.your_input_variable вместо e.target.value - person DominikAngerer; 11.01.2019
comment
Тем, кто использует ES6, важно подчеркнуть использование здесь анонимной функции: если вы используете стрелочную функцию, вы не сможете получить доступ к this внутри функции. - person Polosson; 31.03.2020
comment
нет. OP запросил свойство vue html, как лучше всего для этого. См .: stackoverflow.com/a/50347709/1031191 (не мой ответ, это пока самый чистый, хотя он всего 5 голосов. Думаю, он заслуживает гораздо большего ...) - person Barney Szabolcs; 19.05.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: {
        timeout: null,
        debouncedInput: ''
    },
    computed: {
     input: {
        get() {
          return this.debouncedInput
        },
        set(val) {
          if (this.timeout) clearTimeout(this.timeout)
          this.timeout = setTimeout(() => {
            this.debouncedInput = val
          }, 300)
        }
      }
    }
  }
</script>

Codepen

person digout    schedule 26.11.2018
comment
ты настоящий герой - person Ashtonian; 16.08.2019
comment
Я предпочитаю этот вариант, потому что мне, вероятно, не нужен пакет npm для 11 строк кода .... - person Ben Winding; 06.12.2019
comment
Это должен быть отмеченный ответ, он работает очень хорошо и почти не занимает места. Спасибо! - person Alexander Kludt; 11.02.2020
comment
привет, можно ли добавить помощника TypeScript Version o? - person flourigh; 30.06.2021

Назначение debounce в 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>

person bendytree    schedule 11.04.2018
comment
Не могли бы вы объяснить, почему назначение debounce в методах может быть проблемой? - person MartinTeeVarga; 16.04.2018
comment
См. Примеры ссылок, которые подвержены гниению ссылок. Лучше объяснить проблему в ответе - так будет интереснее для читателей. - person MartinTeeVarga; 17.04.2018
comment
Большое спасибо, мне было плохо, пытаясь понять, почему данные, отображаемые на консоли, были правильными, но не применялись в приложении ... - person ; 04.09.2018
comment
@ sm4, потому что вместо того, чтобы использовать один и тот же совместно используемый экземпляр debounce для желаемой функции, он воссоздает его каждый раз, тем самым в основном убивая использование debounce. - person Mike Sheward; 01.11.2018
comment
Спасибо, я искал этот ответ. Очевидно, когда вы делаете _.debounce одну и ту же функцию в копиях компонента, будет вызываться только последний созданный компонент. - person Flame; 11.01.2019
comment
тогда просто добавьте его в свой data(). - person Su-Au Hwang; 13.03.2019
comment
Просто потратил час на отладку! - person ksb; 21.11.2019
comment
это на самом деле неправильно. Вам не нужно объявлять методы в хуках жизненного цикла, поскольку они связаны со своим собственным экземпляром. Не уверен, откуда OP взяла эту идею, но да, он ошибается на 100%. - person Hybrid web dev; 23.04.2020
comment
Мой голос заблокирован. Лучшим решением на данный момент является решение, получившее 5 голосов: stackoverflow.com/a/50347709/1031191, потому что он аккуратно отделяет логику противодействия от контейнера. - person Barney Szabolcs; 19.05.2020
comment
@Hybridwebdev Я думаю, он получил это из ответа Линуса Борга с форума Vue, поэтому я бы сказал, что это правильное решение forum.vuejs.org/t/ - person ness-EE; 21.06.2021
comment
Спасибо! Таким образом, я могу получить доступ к this внутри отлаженной функции - person Luis Cabrera Benito; 17.07.2021

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

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

  this.timeout = setTimeout(() => {
    // your action
  }, 200); // delay
}
person pshx    schedule 10.04.2019
comment
Как бы я ни любил lodash, это явно лучший ответ для трейлинг-дребезга. Легче всего реализовать и понять. - person Michael Hays; 02.03.2020
comment
также неплохо добавить destroyed() { clearInterval(this.timeout) }, чтобы не было тайм-аута после уничтожения. - person pikilon; 21.05.2020
comment
Из всех решений это единственное, которое надежно работало. - person Jordash; 13.01.2021
comment
Просто, эффективно, здорово! - person thiebo; 29.04.2021

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

Поскольку <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);
},
person stallingOne    schedule 27.10.2018
comment
если в вашем поле ввода также есть действие @input="update_something", вызовите его после that.xxx = val that.update_something(); - person Neon22; 23.06.2019
comment
в разделе методов я использовал немного другой синтаксис, который у меня сработал: 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); }, - person Neon22; 23.06.2019
comment
Это нормально, если у вас есть один или очень мало случаев, когда вам нужно отклонить ввод. Однако вы быстро поймете, что вам нужно переместить это в библиотеку или подобное, если приложение будет расти и эта функциональность понадобится где-то еще. Держите код СУХОЙ. - person Coreus; 11.07.2019

Если вам нужен очень минималистичный подход к этому, я сделал его (изначально созданный на основе 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>
person Coreus    schedule 15.05.2018
comment
Вероятно, это должно быть приемлемое решение, набравшее более 100 голосов. OP запросил такое компактное решение, и оно прекрасно отделяет логику устранения дребезга. - person Barney Szabolcs; 19.05.2020

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


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

В шаблоне:

<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).

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

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

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

person roli roli    schedule 08.07.2019

Хотя почти все ответы здесь уже верны, если кто-то ищет быстрое решение, у меня есть для этого директива. 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>

person undefinederror    schedule 10.09.2019

Чтобы создать отлаженные методы, вы можете использовать вычисленные, таким образом, они не будут совместно использоваться несколькими экземплярами вашего компонента:

<template>
  <input @input="handleInputDebounced">
<template>

<script>
import debounce from 'lodash.debouce';

export default {
  props: {
    timeout: {
      type: Number,
      default: 200,
    },
  },
  methods: {
    handleInput(event) {
      // input handling logic
    },
  },
  computed: {
    handleInputDebounced() {
      return debounce(this.handleInput, this.timeout);
    },
  },
}
</script>

Вы также можете заставить его работать с неконтролируемым v-model:

<template>
  <input v-model="debouncedModel">
<template>

<script>
import debounce from 'lodash.debouce';

export default {
  props: {
    value: String,
    timeout: {
      type: Number,
      default: 200,
    },
  },
  methods: {
    updateValue(value) {
      this.$emit('input', value);
    },
  },
  computed: {
    updateValueDebounced() {
      return debounce(this.updateValue, this.timeout);
    },
    debouncedModel: {
      get() { return this.value; },
      set(value) { this.updateValueDebounced(value); }
    },
  },
}
</script>
person CyberAP    schedule 05.05.2021

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

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

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

person Amir Khadem    schedule 27.05.2019

1 Краткая версия с использованием функции стрелки, со значением задержки по умолчанию

файл: debounce.js в примере: (импортировать debounce из '../../utils/debounce')

export default function (callback, delay=300) {
    let timeout = null
    return (...args) => {
        clearTimeout(timeout)
        const context = this
        timeout = setTimeout(() => callback.apply(context, args), delay)
    }
}

2 Вариант смешивания

файл: debounceMixin.js

export default {
  methods: {
    debounce(func, delay=300) {
      let debounceTimer;
      return function() {
       // console.log("debouncing call..");
        const context = this;
        const args = arguments;
        clearTimeout(debounceTimer);
        debounceTimer = setTimeout(() => func.apply(context, args), delay);
        // console.log("..done");
      };
    }
  }
};

Используйте в vueComponent:

<script>
  import debounceMixin from "../mixins/debounceMixin";
  export default {
   mixins: [debounceMixin],
        data() {
            return {
                isUserIdValid: false,
            };
        },
        mounted() {
        this.isUserIdValid = this.debounce(this.checkUserIdValid, 1000);
        },
    methods: {
        isUserIdValid(id){
        // logic
        }
  }
</script>

другой вариант, пример

Устранение неполадок при поиске Vue

person dev.zom    schedule 10.06.2021

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

import {debounce} from 'utils-decorators';

class SomeService {

  @debounce(500)
  getData(params) {
  }
}
person vlio20    schedule 12.04.2020

 public debChannel = debounce((key) => this.remoteMethodChannelName(key), 200)

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

person Mayxxp    schedule 23.06.2020
comment
Не могли бы вы добавить больше информации об этом решении? - person nunop; 23.06.2020
comment
Пожалуйста, уточните еще немного. Также обратите внимание, что это старая ветка с хорошо известными ответами, поэтому не могли бы вы уточнить, какое решение больше подходит для проблемы? - person jpnadas; 23.06.2020
comment
Будет лучше, если вы объясните, почему это предпочтительное решение, и объясните, как оно работает. Мы хотим обучать, а не просто предоставлять код. - person the Tin Man; 24.06.2020