Jak zaimplementować debounce w Vue2?

143

Mam proste pole wejściowe w szablonie Vue i chciałbym użyć debounce mniej więcej w ten sposób:

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

Jednak debouncewłaściwość została wycofana w Vue 2 . Zalecenie mówi tylko: „użyj v-on: wejście + funkcja odbicia strony trzeciej”.

Jak poprawnie to wdrażasz?

Próbowałem zaimplementować to za pomocą lodash , v-on: input i v-model , ale zastanawiam się, czy da się to zrobić bez dodatkowej zmiennej.

W szablonie:

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

W skrypcie:

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

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

Klucz filtru jest następnie używany później we właściwościach computed.

MartinTeeVarga
źródło
1
Wypróbuj ten stackoverflow.com/questions/41230343/…
sobolevn
3
Proponuję uważnie przeczytać: vuejs.org/v2/guide/ ...
Marek Urbanowicz
3
W przewodniku jest przykład: vuejs.org/v2/guide/computed.html#Watchers
Bengt,

Odpowiedzi:

158

Używam pakietu debounce NPM i zaimplementowałem w następujący sposób:

<input @input="debounceInput">

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

Korzystając z lodash i przykładu w pytaniu, implementacja wygląda następująco:

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

methods: {
  debounceInput: _.debounce(function (e) {
    this.filterKey = e.target.value;
  }, 500)
}
Primoz Rzym
źródło
10
Dzięki za to. Znalazłem podobny przykład w innych dokumentach Vue: vuejs.org/v2/examples/index.html (edytor markdown)
MartinTeeVarga,
5
W proponowanym rozwiązaniu występuje problem, gdy na stronie znajduje się kilka instancji komponentów. Opis problemu i rozwiązanie przedstawiono tutaj: forum.vuejs.org/t/issues-with-vuejs-component-and-debounce/7224/…
Valera
e.currentTarget jest nadpisywany na null w ten sposób
ness-EE
1
Poleciłbym dodać a v-model=your_input_variabledo wejścia i do swojego vue data. Więc nie polegasz na e.targetVue, ale używasz Vue, dzięki czemu możesz uzyskać dostęp this.your_input_variablezamiaste.target.value
DominikAngerer
1
Dla tych, którzy używają ES6, ważne jest, aby podkreślić tutaj użycie funkcji anonimowej: jeśli używasz funkcji strzałkowej, nie będziesz mieć dostępu thisdo funkcji.
Polosson
68

Przypisanie odbicia methodsmoże być kłopotliwe. Więc zamiast tego:

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

Możesz spróbować:

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

Staje się problemem, jeśli masz wiele wystąpień składnika - podobnie jak datapowinna być funkcja zwracająca obiekt. Każda instancja potrzebuje własnej funkcji odbicia, jeśli ma działać niezależnie.

Oto przykład problemu:

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
źródło
1
Czy możesz wyjaśnić, dlaczego przypisanie odbicia w metodach może być kłopotliwe?
MartinTeeVarga
12
Zobacz Przykładowe linki są podatne na gnicie linków. Lepiej wyjaśnić problem w odpowiedzi - to uczyni go bardziej wartościowym dla czytelników.
MartinTeeVarga
Dziękuję bardzo, źle się
@ sm4, ponieważ zamiast używać tej samej udostępnionej, zdemontowanej instancji dla żądanej funkcji, za każdym razem ją odtwarza, zabijając w ten sposób głównie użycie debounce.
Mike Sheward
1
po prostu dodaj to do swojego data().
Su-Au Hwang
45

zaktualizowany w 2020 r

Opcja 1: wielokrotnego użytku, bez dep

(Zalecane w razie potrzeby więcej niż raz w projekcie)

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


Opcja 2: In-component, bez deps

(Zalecane, jeśli używasz raz lub w małym projekcie)

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

wykopać
źródło
4
jesteś prawdziwym bohaterem
Ashtonian
4
Wolę tę opcję, ponieważ prawdopodobnie nie potrzebuję pakietu npm dla 11 linii kodu ....
Ben Winding
3
To powinna być zaznaczona odpowiedź, działa naprawdę dobrze i prawie nie zajmuje miejsca. Dzięki!
Alexander Kludt
29

Bardzo proste bez lodash

  handleScroll: function() {
   if (this.timeout) clearTimeout(this.timeout); 
   this.timeout = setTimeout(() => {
     // your action
   }, 200);
  }
pshx
źródło
4
Tak bardzo, jak kocham lodash, jest to zdecydowanie najlepsza odpowiedź na końcowe odbicie. Najłatwiejszy do wdrożenia i zrozumienia.
Michael Hays
2
również dobrze jest dodać destroyed() { clearInterval(this.timeout) }, aby nie mieć limitu czasu po zniszczeniu.
pikilon
13

Miałem ten sam problem i oto rozwiązanie, które działa bez wtyczek.

Ponieważ <input v-model="xxxx">jest dokładnie tym samym, co

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

(źródło)

Pomyślałem, że mogę ustawić funkcję debounce na przypisaniu xxxx w xxxx = $event.target.value

lubię to

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

metody:

debounceSearch(val){
  if(search_timeout) clearTimeout(search_timeout);
  var that=this;
  search_timeout = setTimeout(function() {
    that.xxxx = val; 
  }, 400);
},
przeciągnięcie jednego
źródło
1
jeśli twoje pole wejściowe również zawierało @input="update_something"akcję, wywołaj to pothat.xxx = val that.update_something();
Neon22
1
w sekcji moich metod użyłem nieco innej składni, która działała dla mnie: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
Jest to w porządku, jeśli masz jeden lub kilka przypadków, w których musisz odrzucić dane wejściowe. Szybko jednak zorientujesz się, że musisz przenieść to do biblioteki lub podobnej, jeśli aplikacja się rozrośnie, a ta funkcja będzie potrzebna gdzie indziej. Utrzymuj swój kod SUCHY.
Coreus,
5

Pamiętaj, że zamieściłem tę odpowiedź przed zaakceptowaną odpowiedzią. To jest niepoprawne. To tylko krok naprzód od rozwiązania tego pytania. Zredagowałem zaakceptowane pytanie, aby pokazać zarówno autorską implementację, jak i ostateczną implementację, z której korzystałem.


Na podstawie komentarzy i powiązanego dokumentu migracji dokonałem kilku zmian w kodzie:

W szablonie:

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

W skrypcie:

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

Metoda, która ustawia klucz filtru, pozostaje taka sama:

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

Wygląda na to, że jest jedno wywołanie mniej (tylko the v-model, a nie the v-on:input).

MartinTeeVarga
źródło
Czy to wezwanie nie byłoby debounceInput()dwa razy przy każdej zmianie? v-on:wykryje zmiany danych wejściowych i odrzucenie wywołania, ORAZ ponieważ model jest powiązany, funkcja obserwująca searchInput również wywoła debounceInput... prawda?
mix3d
@ mix3d Nie rozważaj tej odpowiedzi. To było tylko moje śledztwo, którego nie chciałem kwestionować. Najprawdopodobniej masz rację. Sprawdź zaakceptowaną odpowiedź. Jest poprawny i zredagowałem go, aby pasował do pytania.
MartinTeeVarga
Mój błąd ... Nie zdawałem sobie sprawy, że odpowiedziałeś na własne pytanie, ha!
mix3d
5

Jeśli potrzebujesz bardzo minimalistycznego podejścia do tego, stworzyłem jedno (pierwotnie rozwidlone z porad vuejs, aby również obsługiwać IE), które jest dostępne tutaj: https://www.npmjs.com/package/v-debounce

Stosowanie:

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

Następnie w swoim komponencie:

<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
źródło
Prawdopodobnie to powinno być zaakceptowanym rozwiązaniem, z ponad 100 głosami. OP poprosił o takie kompaktowe rozwiązanie, które ładnie oddziela logikę odbicia.
Barney
1

W przypadku, gdy potrzebujesz zastosować dynamiczne opóźnienie z funkcją lodash debounce:

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

I szablon:

<template>
  //...

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

  //...
</template>

UWAGA: w powyższym przykładzie zrobiłem przykład wyszukiwania, które może wywołać API z niestandardowym opóźnieniem, które jest dostępne wprops

roli roli
źródło
1

Chociaż prawie wszystkie odpowiedzi tutaj są już poprawne, jeśli ktoś szuka szybkiego rozwiązania, mam na to dyrektywę. https://www.npmjs.com/package/vue-lazy-input

Dotyczy @input i v-model, obsługuje niestandardowe komponenty i elementy DOM, debounce i 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
źródło
0

Jeśli używasz Vue, możesz również użyć v.model.lazyzamiast, debounceale pamiętaj, v.model.lazyże nie zawsze będzie działać, ponieważ Vue ogranicza go do niestandardowych komponentów.

W przypadku komponentów niestandardowych należy używać :valuerazem z@change.native

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

Amir Khadem
źródło
0

Gdybyś mógł przenieść wykonywanie funkcji debounce do jakiejś metody klasowej, mógłbyś użyć dekoratora z biblioteki utils-decorators lib ( npm install --save utils-decorators):

import {debounce} from 'utils-decorators';

class SomeService {

  @debounce(500)
  getData(params) {
  }
}
vlio20
źródło
-1

Możemy to zrobić używając kilku linijek kodu JS:

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

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

Proste rozwiązanie! Działa idealnie! Mam nadzieję, że będzie pomocny dla was.

tanvir993
źródło
2
Jasne ... jeśli chcesz zanieczyścić globalną przestrzeń i sprawić, by tylko 1 element mógł z niej korzystać na raz. To okropna odpowiedź.
Hybrydowy programista sieciowy
-1
 public debChannel = debounce((key) => this.remoteMethodChannelName(key), 200)

dekorator vue-property

Mayxxp
źródło
2
Czy mógłbyś dodać więcej informacji o tym rozwiązaniu?
rocha
2
Proszę, opowiedz trochę więcej. Zwróć też uwagę, że jest to stary wątek z dobrze ugruntowanymi odpowiedziami, więc czy możesz wyjaśnić, w jaki sposób Twoje rozwiązanie jest bardziej odpowiednie dla problemu?
jpnadas
Bardziej pomoże, jeśli podasz wyjaśnienie, dlaczego jest to preferowane rozwiązanie i wyjaśnisz, jak to działa. Chcemy uczyć, a nie tylko dostarczać kod.
Tin Man