Como implementar o debounce no Vue2?

143

Eu tenho uma caixa de entrada simples em um modelo do Vue e gostaria de usar o debounce mais ou menos assim:

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

No entanto, a debouncepropriedade foi descontinuada no Vue 2 . A recomendação diz apenas: "use v-on: input + função de debounce de terceiros".

Como você o implementa corretamente?

Eu tentei implementá-lo usando lodash , v-on: input e v-model , mas estou pensando se é possível fazer isso sem a variável extra.

No modelo:

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

No script:

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

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

A chave de filtro é então usada mais tarde em computedadereços.

MartinTeeVarga
fonte
3
Sugiro que leia atentamente: vuejs.org/v2/guide/...
Marek Urbanowicz
3
Há um exemplo no guia: vuejs.org/v2/guide/computed.html#Watchers
Bengt

Respostas:

158

Estou usando o pacote debounce NPM e implementado assim:

<input @input="debounceInput">

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

Usando lodash e o exemplo na pergunta, a implementação se parece com isso:

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

methods: {
  debounceInput: _.debounce(function (e) {
    this.filterKey = e.target.value;
  }, 500)
}
Primoz Rome
fonte
10
Obrigado por isso. I encontrado um exemplo semelhante em alguns outros documentos Vue: vuejs.org/v2/examples/index.html (o editor de remarcação)
MartinTeeVarga
5
A solução proposta tem um problema quando há várias instâncias de componentes na página. O problema é descrito e a solução é apresentada aqui: forum.vuejs.org/t/issues-with-vuejs-component-and-debounce/7224/…
Valera
e.currentTarget será substituído como nulo desta forma
ness-EE
1
Recomendaria adicionar um v-model=your_input_variableà entrada e no seu vue data. Então você não contar com e.target, mas usar Vue para que você possa acessar this.your_input_variableem vez dee.target.value
DominikAngerer
1
Para aqueles que usam o ES6, é importante enfatizar o uso da função anônima aqui: se você usar uma função de seta, não poderá acessar thisdentro da função.
Polosson 31/03
68

Atribuir debounce methodspode ser um problema. Então, em vez disso:

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

Você pode tentar:

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

Torna-se um problema se você tiver várias instâncias de um componente - semelhante à maneira como datadeve ser uma função que retorna um objeto. Cada instância precisa de sua própria função de rejeição, se eles devem agir de forma independente.

Aqui está um exemplo do 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>

Bendytree
fonte
1
Você poderia explicar por que atribuir debounce em métodos pode ser um problema?
MartinTeeVarga
12
Veja Exemplos de links sujeitos a rot-link. É melhor explicar o problema na resposta - isso o tornará mais valioso para os leitores.
MartinTeeVarga
Obrigado, muito obrigado, eu tive um mau momento tentando entender por que os dados exibidos no console estavam corretos, mas não foram aplicados no aplicativo ...
@ sm4 porque, em vez de usar a mesma instância compartilhada debounce para a função desejada, ela a recria a cada vez, eliminando principalmente o uso de debounce.
23411 Mike Onward -
1
basta adicioná-lo ao seu data()então.
Su-Au Hwang
44

atualizado em 2020

Opção 1: reutilizável, sem deps

(Recomendado se necessário mais de uma vez no seu projeto)

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


Opção 2: no componente, sem deps

(Recomendado se usar uma vez ou em um projeto pequeno)

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

desenterrar
fonte
4
você é o verdadeiro herói
Ashtonian
4
Eu prefiro esta opção porque eu provavelmente não precisa de um pacote de NPM por 11 linhas de código ....
Ben enrolamento
3
Essa deve ser a resposta marcada, funciona muito bem e não ocupa quase nenhum espaço. Obrigado!
Alexander Kludt
29

Muito simples sem lodash

  handleScroll: function() {
   if (this.timeout) clearTimeout(this.timeout); 
   this.timeout = setTimeout(() => {
     // your action
   }, 200);
  }
pshx
fonte
4
Por mais que eu goste de lodash, essa é claramente a melhor resposta para um debounce final. Mais fácil de implementar e entender.
Michael Hays
2
Também é uma boa coisa a acrescentar destroyed() { clearInterval(this.timeout) }para não ter um tempo limite após destruído.
pikilon
13

Eu tive o mesmo problema e aqui está uma solução que funciona sem plugins.

Como <input v-model="xxxx">é exatamente o mesmo que

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

(fonte)

Eu imaginei que poderia definir uma função de debounce na atribuição de xxxx em xxxx = $event.target.value

como isso

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

métodos:

debounceSearch(val){
  if(search_timeout) clearTimeout(search_timeout);
  var that=this;
  search_timeout = setTimeout(function() {
    that.xxxx = val; 
  }, 400);
},
stallingOne
fonte
1
se o seu campo de entrada também tiver uma @input="update_something"ação, chame-o depoisthat.xxx = val that.update_something();
Neon22 23/06
1
em meus métodos de seção Eu usei uma sintaxe ligeiramente diferente, que trabalhou para mim: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
Tudo bem se você estiver tendo uma ou muito poucas instâncias em que precisa renunciar à entrada. No entanto, você perceberá rapidamente que precisará movê-lo para uma biblioteca ou similar se o aplicativo crescer e essa funcionalidade for necessária em outro lugar. Mantenha seu código SECO.
Coreus 11/07/19
5

Observe que eu postei essa resposta antes da resposta aceita. Não é correto. É apenas um passo à frente da solução na pergunta. Eu editei a pergunta aceita para mostrar a implementação do autor e a implementação final que eu havia usado.


Com base nos comentários e no documento de migração vinculado , fiz algumas alterações no código:

No modelo:

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

No script:

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

E o método que define a chave de filtro permanece o mesmo:

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

Parece que há menos uma chamada (apenas a v-model, e não a v-on:input).

MartinTeeVarga
fonte
Essa ligação não seria debounceInput()duas vezes para cada alteração? v-on:detectará as alterações de entrada e rejeitará chamadas, E como o modelo é vinculado, a função de relógio do searchInput TAMBÉM chamará debounceInput... certo?
Mix3d
@ mix3d Não considere esta resposta. Foi apenas minha investigação que não quis colocar na pergunta. Você provavelmente está certo. Verifique a resposta aceita. Está correto e editei-o para corresponder à pergunta.
MartinTeeVarga
Meu erro ... eu não sabia que você tinha respondido sua própria pergunta, ha!
mix3d
5

Se você precisar de uma abordagem muito minimalista, criei uma (originalmente bifurcada no vuejs-tips para também oferecer suporte ao IE) disponível aqui: https://www.npmjs.com/package/v-debounce

Uso:

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

Então no seu 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>
Coreus
fonte
Provavelmente, essa deve ser a solução aceita, com mais de 100 votos. O OP pediu uma solução compacta como essa e dissocia muito bem a lógica de rejeição.
Barney
1

Caso você precise aplicar um atraso dinâmico com a debouncefunção do 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 o modelo:

<template>
  //...

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

  //...
</template>

NOTA: no exemplo acima, fiz um exemplo de entrada de pesquisa que pode chamar a API com um atraso personalizado, fornecido emprops

roli roli
fonte
1

Embora praticamente todas as respostas aqui já estejam corretas, se alguém estiver em busca de uma solução rápida, tenho uma diretiva para isso. https://www.npmjs.com/package/vue-lazy-input

Aplica-se a @input e v-model, suporta componentes personalizados e elementos 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>

undefinederror
fonte
0

Se você estiver usando o Vue, também poderá usar em v.model.lazyvez de, debouncemas lembre-sev.model.lazy se de que nem sempre funcionará, pois o Vue o limita para componentes personalizados.

Para componentes personalizados, você deve usar :valuejunto com@change.native

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

Amir Khadem
fonte
0

Se você pudesse mover a execução da função debounce para algum método de classe, poderia usar um decorador a partir da utils-decorators lib ( npm install --save utils-decorators):

import {debounce} from 'utils-decorators';

class SomeService {

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

Podemos fazer isso usando poucas linhas de código JS:

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

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

Solução simples! Trabalho perfeito! Espero que seja útil para vocês.

tanvir993
fonte
2
Claro ... se você quiser poluir o espaço global e fazer com que apenas 1 elemento possa usá-lo por vez. Esta é uma resposta terrível.
Hybrid web dev
-1
 public debChannel = debounce((key) => this.remoteMethodChannelName(key), 200)

decor-propriedade-vue

Mayxxp
fonte
2
Você poderia adicionar mais informações sobre esta solução?
rocha
2
Por favor, elabore um pouco mais. Além disso, observe que esse é um tópico antigo com respostas bem estabelecidas. Você pode esclarecer como sua solução é mais apropriada para o problema?
jpnadas 23/06
Isso ajuda mais se você fornecer uma explicação sobre por que essa é a solução preferida e explicar como ela funciona. Queremos educar, não apenas fornecer código.
the Tin Man