Wie implementiere ich Debounce in Vue2?

143

Ich habe ein einfaches Eingabefeld in einer Vue-Vorlage und möchte Debounce mehr oder weniger so verwenden:

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

Die debounceImmobilie wurde jedoch in Vue 2 veraltet . Die Empfehlung lautet nur: "Verwenden Sie v-on: Eingabe + Entprellungsfunktion eines Drittanbieters".

Wie setzen Sie es richtig um?

Ich habe versucht, es mit lodash , v-on: input und v-model zu implementieren , aber ich frage mich, ob es möglich ist, auf die zusätzliche Variable zu verzichten.

In Vorlage:

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

Im Skript:

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

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

Der Filterschlüssel wird später in computedRequisiten verwendet.

MartinTeeVarga
quelle
1
Versuchen Sie dieses eine stackoverflow.com/questions/41230343/…
sobolevn
3
Ich würde vorschlagen, sorgfältig zu lesen: vuejs.org/v2/guide/…
Marek Urbanowicz
3
Es gibt ein Beispiel in der Anleitung: vuejs.org/v2/guide/computed.html#Watchers
Bengt

Antworten:

158

Ich verwende das Debounce- NPM-Paket und implementiere es wie folgt :

<input @input="debounceInput">

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

Mit lodash und dem Beispiel in der Frage sieht die Implementierung folgendermaßen aus:

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

methods: {
  debounceInput: _.debounce(function (e) {
    this.filterKey = e.target.value;
  }, 500)
}
Primoz Rom
quelle
10
Danke dafür. Ich habe ein ähnliches Beispiel in einigen anderen Vue-Dokumenten gefunden: vuejs.org/v2/examples/index.html (der Markdown-Editor)
MartinTeeVarga
5
Die vorgeschlagene Lösung weist ein Problem auf, wenn mehrere Komponenteninstanzen auf der Seite vorhanden sind. Das Problem wird hier beschrieben und die Lösung vorgestellt: forum.vuejs.org/t/issues-with-vuejs-component-and-debounce/7224/…
Valera
e.currentTarget wird auf diese Weise auf null überschrieben
ness-EE
1
Würde empfehlen, ein v-model=your_input_variablezum Eingang und in Ihrem Vue hinzuzufügen data. So Sie verlassen sich auf nicht e.targetaber Vue verwenden , so dass Sie zugreifen können , this.your_input_variableanstatte.target.value
DominikAngerer
1
Für diejenigen, die ES6 verwenden, ist es wichtig, die Verwendung der anonymen Funktion hier hervorzuheben: Wenn Sie eine Pfeilfunktion verwenden, können Sie thisinnerhalb der Funktion nicht darauf zugreifen .
Polosson
68

Das Zuweisen von Entprellen methodskann problematisch sein. Also stattdessen:

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

Sie können versuchen:

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

Es wird zu einem Problem, wenn Sie mehrere Instanzen einer Komponente haben - ähnlich wie dataeine Funktion, die ein Objekt zurückgibt. Jede Instanz benötigt eine eigene Entprellfunktion, wenn sie unabhängig agieren soll.

Hier ist ein Beispiel für das Problem:

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
quelle
1
Können Sie erklären, warum das Zuweisen von Debounce in Methoden problematisch sein kann?
MartinTeeVarga
12
Siehe Beispiel Links sind anfällig für Link-Rot. Es ist besser, das Problem in der Antwort zu erklären - es wird es für die Leser wertvoller machen.
MartinTeeVarga
Vielen Dank, ich hatte eine schlechte Zeit zu verstehen, warum die auf der Konsole angezeigten Daten richtig waren, aber nicht auf die App angewendet wurden ...
@ sm4, da anstelle der Verwendung derselben gemeinsam genutzten entprellten Instanz für die gewünschte Funktion diese jedes Mal neu erstellt wird, wodurch die Verwendung von entprellen hauptsächlich aufgehoben wird.
Mike Sheward
1
füge es einfach deinem data()dann hinzu.
Su-Au Hwang
44

aktualisiert im Jahr 2020

Option 1: Wiederverwendbar, keine Deps

(Empfohlen bei Bedarf mehr als einmal in Ihrem Projekt)

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


Option 2: In-Component, keine Deps

(Empfohlen bei einmaliger Verwendung oder in kleinen Projekten)

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

ausgraben
quelle
4
Sie der wahre Held
Ashtonian
4
Ich bevorzuge diese Option, weil ich wahrscheinlich kein npm-Paket für 11 Codezeilen benötige ....
Ben Winding
3
Dies sollte die markierte Antwort sein, dies funktioniert wirklich gut und nimmt fast keinen Platz ein. Vielen Dank!
Alexander Kludt
29

Sehr einfach ohne lodash

  handleScroll: function() {
   if (this.timeout) clearTimeout(this.timeout); 
   this.timeout = setTimeout(() => {
     // your action
   }, 200);
  }
pshx
quelle
4
So sehr ich lodash liebe, ist dies eindeutig die beste Antwort für ein nachlaufendes Debounce. Am einfachsten zu implementieren und zu verstehen.
Michael Hays
2
Es ist auch eine gute Sache, etwas hinzuzufügen, destroyed() { clearInterval(this.timeout) }um nach der Zerstörung keine Zeitüberschreitung zu haben.
Pikilon
13

Ich hatte das gleiche Problem und hier ist eine Lösung, die ohne Plugins funktioniert.

Da <input v-model="xxxx">ist genau das gleiche wie

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

(Quelle)

Ich dachte, ich könnte eine Entprellungsfunktion für die Zuweisung von xxxx in einstellen xxxx = $event.target.value

so was

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

Methoden:

debounceSearch(val){
  if(search_timeout) clearTimeout(search_timeout);
  var that=this;
  search_timeout = setTimeout(function() {
    that.xxxx = val; 
  }, 400);
},
StallingOne
quelle
Wenn Ihr Eingabefeld auch eine @input="update_something"Aktion hatte, rufen Sie diese nachthat.xxx = val that.update_something();
Neon22
1
In meinem Methodenabschnitt habe ich eine etwas andere Syntax verwendet, die für mich funktioniert hat: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
Dies ist in Ordnung, wenn Sie eine oder sehr wenige Fälle haben, in denen Sie Eingaben entprellen müssen. Sie werden jedoch schnell feststellen, dass Sie dies in eine Bibliothek oder ähnliches verschieben müssen, wenn die App wächst und diese Funktionalität an anderer Stelle benötigt wird. Behalten Sie Ihren Code trocken.
Coreus
5

Bitte beachten Sie, dass ich diese Antwort vor der akzeptierten Antwort gepostet habe. Das ist nicht richtig. Es ist nur ein Schritt vorwärts von der Lösung in der Frage. Ich habe die akzeptierte Frage bearbeitet, um sowohl die Implementierung des Autors als auch die endgültige Implementierung anzuzeigen, die ich verwendet habe.


Basierend auf Kommentaren und dem verknüpften Migrationsdokument habe ich einige Änderungen am Code vorgenommen:

In Vorlage:

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

Im Skript:

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

Die Methode zum Festlegen des Filterschlüssels bleibt unverändert:

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

Es sieht so aus, als gäbe es einen Anruf weniger (nur den v-modelund nicht den v-on:input).

MartinTeeVarga
quelle
Würde dies nicht debounceInput()zweimal für jede Änderung aufgerufen werden? v-on:erkennt die Eingabeänderungen und ruft das Entprellen auf, UND da das Modell gebunden ist, ruft die Überwachungsfunktion von searchInput AUCH auf debounceInput... richtig?
mix3d
@ mix3d Berücksichtige diese Antwort nicht. Es war nur meine Untersuchung, die ich nicht in die Frage stellen wollte. Sie haben höchstwahrscheinlich recht. Überprüfen Sie die akzeptierte Antwort. Es ist richtig und ich habe es bearbeitet, um es der Frage anzupassen.
MartinTeeVarga
Mein Fehler ... Ich wusste nicht, dass du deine eigene Frage beantwortet hast, ha!
mix3d
5

Wenn Sie einen sehr minimalistischen Ansatz benötigen, habe ich einen erstellt (ursprünglich aus vuejs-tips gegabelt, um auch den IE zu unterstützen), der hier verfügbar ist: https://www.npmjs.com/package/v-debounce

Verwendung:

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

Dann in Ihrer Komponente:

<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>
Coreus
quelle
Wahrscheinlich sollte dies die akzeptierte Lösung mit mehr als 100 Stimmen sein. Das OP forderte eine kompakte Lösung wie diese und entkoppelt die Entprelllogik.
Barney
1

Falls Sie eine dynamische Verzögerung mit der debounceFunktion des lodash anwenden müssen:

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) {
    // ...
  }
}

Und die Vorlage:

<template>
  //...

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

  //...
</template>

HINWEIS: Im obigen Beispiel habe ich ein Beispiel für eine Sucheingabe erstellt, mit der die API mit einer benutzerdefinierten Verzögerung aufgerufen werden kann, die in bereitgestellt wirdprops

roli roli
quelle
1

Obwohl so ziemlich alle Antworten hier bereits richtig sind, habe ich eine Richtlinie dafür, wenn jemand auf der Suche nach einer schnellen Lösung ist. https://www.npmjs.com/package/vue-lazy-input

Es gilt für @input und v-model, unterstützt benutzerdefinierte Komponenten und DOM-Elemente, entprellen und drosseln.

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
quelle
0

Wenn Sie Vue verwenden, können Sie auch v.model.lazyanstelle von verwenden, debounceaber denken Sie daran, v.model.lazydass dies nicht immer funktioniert, da Vue es für benutzerdefinierte Komponenten einschränkt.

Für benutzerdefinierte Komponenten sollten Sie :valuezusammen mit verwenden@change.native

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

Amir Khadem
quelle
0

Wenn Sie die Ausführung der Entprellungsfunktion in eine Klassenmethode verschieben könnten, könnten Sie einen Dekorator aus den utils-decorators lib ( npm install --save utils-decorators) verwenden:

import {debounce} from 'utils-decorators';

class SomeService {

  @debounce(500)
  getData(params) {
  }
}
vlio20
quelle
-1

Wir können mit wenigen Zeilen JS-Code umgehen:

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

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

Einfache Lösung! Perfekt arbeiten! Hoffe, wird für euch hilfreich sein.

tanvir993
quelle
2
Sicher ... wenn Sie den globalen Raum verschmutzen und so gestalten möchten, dass nur 1 Element ihn gleichzeitig verwenden kann. Das ist eine schreckliche Antwort.
Hybrid Web Dev
-1
 public debChannel = debounce((key) => this.remoteMethodChannelName(key), 200)

vue-property-decorator

Mayxxp
quelle
2
Könnten Sie bitte weitere Informationen zu dieser Lösung hinzufügen?
Rocha
2
Bitte erläutern Sie etwas mehr. Beachten Sie auch, dass dies ein alter Thread mit gut etablierten Antworten ist. Können Sie also klären, wie Ihre Lösung für das Problem besser geeignet ist?
jpnadas
Es ist hilfreicher, wenn Sie erklären, warum dies die bevorzugte Lösung ist, und erklären, wie es funktioniert. Wir wollen aufklären, nicht nur Code bereitstellen.
Der Blechmann