Come implementare il debounce in Vue2?


143

Ho una semplice casella di input in un modello Vue e vorrei usare il debounce più o meno in questo modo:

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

Tuttavia, la debounceproprietà è stata deprecata in Vue 2 . La raccomandazione dice solo: "usa v-on: input + funzione di debounce di terze parti".

Come lo implementate correttamente?

Ho provato a implementarlo usando lodash , v-on: input e v-model , ma mi chiedo se sia possibile fare a meno della variabile extra.

Nel modello:

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

Nello script:

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

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

La chiave filtro viene quindi utilizzata in seguito negli computedoggetti di scena.



3
Suggerirei di leggere attentamente: vuejs.org/v2/guide/…
Marek Urbanowicz,

3
C'è un esempio nella guida: vuejs.org/v2/guide/computed.html#Watchers
Bengt,

Risposte:


158

Sto usando il pacchetto NPM debounce e implementato in questo modo:

<input @input="debounceInput">

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

Utilizzando lodash e l'esempio nella domanda, l'implementazione è simile alla seguente:

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

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

10
Grazie per questo. Ho trovato un esempio simile in alcuni altri documenti Vue: vuejs.org/v2/examples/index.html (l'editor di
markdown

5
La soluzione proposta presenta un problema quando nella pagina sono presenti più istanze del componente. Il problema è descritto e la soluzione presentata qui: forum.vuejs.org/t/issues-with-vuejs-component-and-debounce/7224/…
Valera

e.currentTarget viene sovrascritto in questo modo null
ness-EE

1
Consiglierei di aggiungere un v-model=your_input_variableinput e nella tua vue data. Quindi non ti affidi e.targetma usi Vue in modo da poter accedere this.your_input_variableinvece die.target.value
DominikAngerer

1
Per coloro che utilizzano ES6, è importante enfatizzare l'uso della funzione anonima qui: se si utilizza una funzione freccia, non sarà possibile accedere thisall'interno della funzione.
Polosson

68

Assegnare il rimbalzo methodspuò essere un problema. Quindi invece di questo:

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

Puoi provare:

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

Diventa un problema se hai più istanze di un componente - simile al modo in cui datadovrebbe essere una funzione che restituisce un oggetto. Ogni istanza necessita della propria funzione di debounce se si suppone che agisca in modo indipendente.

Ecco un esempio del problema:

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>


1
Potresti spiegare perché l'assegnazione del rimbalzo nei metodi può essere un problema?
MartinTeeVarga,

12
Vedi Esempio: i collegamenti sono inclini al collegamento-marciume. È meglio spiegare il problema nella risposta: lo renderà più prezioso per i lettori.
MartinTeeVarga,

Grazie mille partita, ho avuto un brutto momento nel cercare di capire perché i dati visualizzati sulla console erano giusti ma non applicati sull'app ...

@ sm4 perché invece di utilizzare la stessa istanza debounce condivisa per la funzione desiderata, la ricrea ogni volta, uccidendo così principalmente l'uso del debounce.
Mike Sheward,

1
aggiungilo al tuo data()allora.
Su-Au Hwang,

45

aggiornato nel 2020

Opzione 1: riutilizzabile, nessun deps

(Consigliato se necessario più di una volta nel progetto)

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


Opzione 2: nel componente, nessun deps

(Consigliato se si utilizza una volta o in piccoli progetti)

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


4
tu il vero eroe
Ashtonian,

4
Preferisco questa opzione perché probabilmente non ho bisogno di un pacchetto npm per 11 righe di codice ....
Ben Winding

3
Questa dovrebbe essere la risposta marcata, funziona davvero bene e non occupa quasi spazio. Grazie!
Alexander Kludt,

29

Molto semplice senza lodash

  handleScroll: function() {
   if (this.timeout) clearTimeout(this.timeout); 
   this.timeout = setTimeout(() => {
     // your action
   }, 200);
  }

4
Per quanto adoro lodash, questa è chiaramente la migliore risposta per un rimbalzo finale. Più facile da implementare e comprendere.
Michael Hays,

2
è anche una buona cosa aggiungere destroyed() { clearInterval(this.timeout) }per non avere un timeout dopo la distruzione.
pikilon,

13

Ho avuto lo stesso problema ed ecco una soluzione che funziona senza plugin.

Since <input v-model="xxxx">è esattamente lo stesso di

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

(fonte)

Ho pensato di poter impostare una funzione di debounce sull'assegnazione di xxxx in xxxx = $event.target.value

come questo

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

metodi:

debounceSearch(val){
  if(search_timeout) clearTimeout(search_timeout);
  var that=this;
  search_timeout = setTimeout(function() {
    that.xxxx = val; 
  }, 400);
},

1
se anche il tuo campo di input ha avuto @input="update_something"un'azione, chiama questo dopothat.xxx = val that.update_something();
Neon22

1
nella mia sezione dei metodi ho usato una sintassi leggermente diversa che ha funzionato per me: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

Questo va bene se hai uno o pochissimi casi in cui è necessario rimandare l'input. Tuttavia, ti accorgerai rapidamente che dovrai spostarlo in una libreria o simile se l'app cresce e questa funzionalità è necessaria altrove. Conserva il codice ASCIUTTO.
Coreus,

5

Si noti che ho inviato questa risposta prima della risposta accettata. Non è corretto È solo un passo avanti rispetto alla soluzione nella domanda. Ho modificato la domanda accettata per mostrare sia l'implementazione dell'autore sia l'implementazione finale che avevo usato.


Sulla base dei commenti e del documento di migrazione collegato , ho apportato alcune modifiche al codice:

Nel modello:

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

Nello script:

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

E il metodo che imposta la chiave del filtro rimane lo stesso:

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

Sembra che ci sia una chiamata in meno (solo il v-model, e non il v-on:input).


Questa chiamata non sarebbe debounceInput()due volte per ogni modifica? v-on:rileverà le modifiche di input e chiamerà il debounce, E poiché il modello è associato, la funzione watch di searchInput chiamerà ANCHE debounceInput... giusto?
mix3d

@ mix3d Non considerare questa risposta. Era solo la mia indagine che non volevo porre nella domanda. Molto probabilmente hai ragione. Controlla la risposta accettata. È corretto e l'ho modificato in base alla domanda.
MartinTeeVarga,

Il mio errore ... Non mi ero reso conto che avevi risposto alla tua domanda, ah!
mix3d

5

Se hai bisogno di un approccio molto minimalista a questo, ne ho creato uno (originariamente biforcuto da vuejs-tips per supportare anche IE) che è disponibile qui: https://www.npmjs.com/package/v-debounce

Uso:

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

Quindi nel tuo componente:

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

Probabilmente questa dovrebbe essere la soluzione accettata, con oltre 100 voti. L'OP ha richiesto una soluzione compatta come questa e disaccoppia bene la logica di debounce.
Barney,

1

Nel caso in cui sia necessario applicare un ritardo dinamico con la debouncefunzione 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) {
    // ...
  }
}

E il modello:

<template>
  //...

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

  //...
</template>

NOTA: nell'esempio sopra ho fatto un esempio di input di ricerca che può chiamare l'API con un ritardo personalizzato fornito inprops


1

Sebbene praticamente tutte le risposte qui siano già corrette, se qualcuno è alla ricerca di una soluzione rapida, ho una direttiva per questo. https://www.npmjs.com/package/vue-lazy-input

Si applica a @input e v-model, supporta componenti personalizzati ed elementi DOM, debounce e 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>


0

Se stai usando Vue puoi anche usare v.model.lazyinvece di, debouncema ricordav.model.lazy che non funzionerà sempre poiché Vue lo limita per i componenti personalizzati.

Per i componenti personalizzati che dovresti usare :valueinsieme a@change.native

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


0

Se potessi spostare l'esecuzione della funzione di debounce in un metodo di classe, puoi usare un decoratore dal lib utils-decorators ( npm install --save utils-decorators):

import {debounce} from 'utils-decorators';

class SomeService {

  @debounce(500)
  getData(params) {
  }
}

-1

Possiamo farlo usando poche righe di codice JS:

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

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

Soluzione semplice! Funziona perfettamente! Spero, sarà utile per voi ragazzi.


2
Certo ... se vuoi inquinare lo spazio globale e renderlo così solo un elemento alla volta può usarlo. Questa è una risposta terribile.
Dev web ibrido il

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

vue-proprietà-decorator


2
Potresti aggiungere ulteriori informazioni su questa soluzione?
rocha,

2
Per favore, elabora un po 'di più. Inoltre, tieni presente che si tratta di un vecchio thread con risposte consolidate, quindi puoi chiarire come la tua soluzione è più appropriata per il problema?
jpnadas,

Aiuta di più se fornisci una spiegazione del perché questa è la soluzione preferita e spiega come funziona. Vogliamo educare, non solo fornire codice.
Tin Man,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.