Comment implémenter le debounce dans Vue2?


143

J'ai une boîte de saisie simple dans un modèle Vue et je voudrais utiliser un debounce plus ou moins comme ceci:

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

Cependant, la debouncepropriété est obsolète dans Vue 2 . La recommandation dit seulement: "utiliser v-on: entrée + fonction anti-rebond tierce".

Comment le mettez-vous correctement en œuvre?

J'ai essayé de l'implémenter en utilisant lodash , v-on: input et v-model , mais je me demande s'il est possible de se passer de la variable supplémentaire.

Dans le modèle:

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

Dans le script:

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

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

La clé de filtre est ensuite utilisée plus tard dans les computedaccessoires.



3
Je suggère de lire attentivement: vuejs.org/v2/guide/…
Marek Urbanowicz

3
Il y a un exemple dans le guide: vuejs.org/v2/guide/computed.html#Watchers
Bengt

Réponses:


158

J'utilise le package debounce NPM et implémenté comme ceci:

<input @input="debounceInput">

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

En utilisant lodash et l'exemple de la question, l'implémentation ressemble à ceci:

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

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

10
Merci pour cela. J'ai trouvé un exemple similaire dans d'autres documents Vue: vuejs.org/v2/examples/index.html (l'éditeur de markdown)
MartinTeeVarga

5
La solution proposée pose un problème lorsqu'il y a plusieurs instances de composant sur la page. Le problème est décrit et la solution est présentée ici: forum.vuejs.org/t/issues-with-vuejs-component-and-debounce/7224/…
Valera

e.currentTarget est remplacé par null de cette façon
ness-EE

1
Je recommanderais d'ajouter un v-model=your_input_variableà l'entrée et à votre vue data. Donc, vous ne comptez pas sur, e.targetmais utilisez Vue pour pouvoir accéder à la this.your_input_variableplace dee.target.value
DominikAngerer

1
Pour ceux qui utilisent ES6, il est important de souligner ici l'utilisation de la fonction anonyme: si vous utilisez une fonction fléchée, vous ne pourrez pas accéder thisà la fonction.
Polosson

68

L'attribution d'un anti-rebond methodspeut poser problème. Donc au lieu de ça:

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

Vous pouvez essayer:

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

Cela devient un problème si vous avez plusieurs instances d'un composant - de la même manière que datadevrait être une fonction qui renvoie un objet. Chaque instance a besoin de sa propre fonction anti-rebond si elle est censée agir indépendamment.

Voici un exemple du problème:

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
Pourriez-vous expliquer pourquoi l'attribution d'un anti-rebond dans les méthodes peut être problématique?
MartinTeeVarga

12
Voir les exemples de liens sont sujets à la pourriture des liens. Il vaut mieux expliquer le problème dans la réponse - cela le rendra plus précieux pour les lecteurs.
MartinTeeVarga

Merci beaucoup, j'ai eu du mal à essayer de comprendre pourquoi les données affichées sur la console étaient correctes mais pas appliquées sur l'application ...

@ sm4 car au lieu d'utiliser la même instance debounce partagée pour la fonction souhaitée, il la recrée à chaque fois, tuant ainsi l'utilisation de debounce principalement.
Mike Sheward

1
ajoutez-le simplement à votre data()alors.
Su-Au Hwang

46

mis à jour en 2020

Option 1: réutilisable, pas de profondeur

(Recommandé si nécessaire plus d'une fois dans votre projet)

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: dans le composant, pas de déps

(Recommandé si vous utilisez une fois ou dans un petit projet)

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
vous le vrai héros
Ashtonian

4
Je préfère cette option car je n'ai probablement pas besoin d'un package npm pour 11 lignes de code ....
Ben Winding

3
Cela devrait être la réponse marquée, cela fonctionne très bien et ne prend presque pas de place du tout. Merci!
Alexander Kludt le

29

Très simple sans lodash

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

4
Autant j'aime le lodash, c'est clairement la meilleure réponse pour un anti-rebond de fuite. Le plus simple à mettre en œuvre et à comprendre.
Michael Hays

2
est également une bonne chose à ajouter destroyed() { clearInterval(this.timeout) }afin de ne pas avoir de délai d'attente après la destruction.
pikilon

13

J'ai eu le même problème et voici une solution qui fonctionne sans plugins.

Depuis <input v-model="xxxx">est exactement le même que

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

(la source)

J'ai pensé que je pourrais définir une fonction anti-rebond sur l'attribution de xxxx dans xxxx = $event.target.value

comme ça

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

méthodes:

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

1
si votre champ de saisie avait également une @input="update_something"action, appelez-le aprèsthat.xxx = val that.update_something();
Neon22

1
dans ma section méthodes, j'ai utilisé une syntaxe légèrement différente qui a fonctionné pour moi: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

C'est correct si vous avez une ou très peu d'instances où vous devez anti-rebondir une entrée. Cependant, vous vous rendrez vite compte que vous devrez déplacer cela vers une bibliothèque ou similaire si l'application se développe et que cette fonctionnalité est nécessaire ailleurs. Gardez votre code SEC.
Coreus

5

Veuillez noter que j'ai posté cette réponse avant la réponse acceptée. Ce n'est pas correct. C'est juste un pas en avant par rapport à la solution de la question. J'ai édité la question acceptée pour montrer à la fois l'implémentation de l'auteur et l'implémentation finale que j'avais utilisée.


Sur la base des commentaires et du document de migration lié , j'ai apporté quelques modifications au code:

Dans le modèle:

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

Dans le script:

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

Et la méthode qui définit la clé de filtre reste la même:

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

On dirait qu'il y a un appel de moins (juste le v-model, et pas le v-on:input).


Est-ce que cela n'appellerait pas debounceInput()deux fois pour chaque changement? v-on:détectera les changements d'entrée et appellera le debounce, ET parce que le modèle est lié, la fonction de surveillance de searchInput appellera AUSSI debounceInput... non?
mix3d

@ mix3d Ne considérez pas cette réponse. C'était juste mon enquête que je ne voulais pas poser dans la question. Vous avez probablement raison. Vérifiez la réponse acceptée. C'est correct et je l'ai modifié pour correspondre à la question.
MartinTeeVarga

Mon erreur ... Je n'avais pas réalisé que tu avais répondu à ta propre question, ha!
mix3d

5

Si vous avez besoin d'une approche très minimaliste à ce sujet, j'en ai fait une (à l'origine dérivée de vuejs-tips pour également prendre en charge IE) qui est disponible ici: https://www.npmjs.com/package/v-debounce

Usage:

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

Puis dans votre composant:

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

Probablement celle-ci devrait être la solution acceptée, avec plus de 100 votes. L'OP a demandé une solution compacte comme celle-ci, et elle dissocie bien la logique anti-rebond.
Barney

1

Au cas où vous auriez besoin d'appliquer un délai dynamique avec la debouncefonction du 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) {
    // ...
  }
}

Et le modèle:

<template>
  //...

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

  //...
</template>

REMARQUE: dans l'exemple ci-dessus, j'ai fait un exemple d'entrée de recherche qui peut appeler l'API avec un délai personnalisé qui est fourni dansprops


1

Bien que pratiquement toutes les réponses ici soient déjà correctes, si quelqu'un est à la recherche d'une solution rapide, j'ai une directive à ce sujet. https://www.npmjs.com/package/vue-lazy-input

Il s'applique à @input et v-model, prend en charge les composants personnalisés et les éléments DOM, anti-rebond et accélérateur.

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

Si vous utilisez Vue, vous pouvez également utiliser à la v.model.lazyplace de, debouncemais rappelez v.model.lazy- vous que cela ne fonctionnera pas toujours car Vue le limite pour les composants personnalisés.

Pour les composants personnalisés, vous devez utiliser :valueavec@change.native

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


0

Si vous pouviez déplacer l'exécution de la fonction anti-rebond dans une méthode de classe, vous pourriez utiliser un décorateur de utils-decorators lib ( npm install --save utils-decorators):

import {debounce} from 'utils-decorators';

class SomeService {

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

-1

Nous pouvons faire avec en utilisant quelques lignes de code JS:

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

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

Solution simple! Travaillez parfaitement! Hope, sera utile pour vous les gars.


2
Bien sûr ... si vous voulez polluer l'espace global et faire en sorte qu'un seul élément puisse l'utiliser à la fois. C'est une réponse terrible.
Développement web hybride

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

vue-propriété-décorateur


2
Pourriez-vous s'il vous plaît ajouter plus d'informations sur cette solution?
rocha le

2
Veuillez élaborer un peu plus. Notez également qu'il s'agit d'un ancien fil avec des réponses bien établies, alors pouvez-vous clarifier en quoi votre solution est plus appropriée au problème?
jpnadas

Cela aide davantage si vous expliquez pourquoi c'est la solution préférée et expliquez son fonctionnement. Nous voulons éduquer, pas seulement fournir du code.
the Tin Man le
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.