วิธีใช้ debounce ใน Vue2


143

ฉันมีช่องป้อนข้อมูลอย่างง่ายในเทมเพลต Vue และฉันต้องการใช้ debounce มากหรือน้อยเช่นนี้

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

แต่debounceคุณสมบัติที่ได้รับการคัดค้านใน Vue 2 คำแนะนำนี้บอกว่า: "ใช้ v-on: input + ฟังก์ชั่น debounce บุคคลที่สาม"

คุณใช้มันอย่างถูกต้องได้อย่างไร?

ฉันพยายามที่จะใช้มันโดยใช้lodash , v-on: inputและv-modelแต่ฉันสงสัยว่ามันเป็นไปได้ที่จะทำโดยไม่มีตัวแปรพิเศษหรือไม่

ในเทมเพลต:

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

ในสคริปต์:

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

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

ตัวกรองจะถูกใช้ในcomputedอุปกรณ์ประกอบฉากในภายหลัง



3
ฉันขอแนะนำให้อ่านอย่างละเอียด: vuejs.org/v2/guide/ …
Marek Urbanowicz

3
มีตัวอย่างในคำแนะนำคือ: vuejs.org/v2/guide/computed.html#Watchers
Bengt

คำตอบ:


158

ฉันใช้แพ็กเกจdebounce NPM และมีการใช้งานเช่นนี้:

<input @input="debounceInput">

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

ใช้lodashและตัวอย่างในคำถามการใช้งานมีลักษณะดังนี้:

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

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

10
ขอบคุณสำหรับสิ่งนี้. ฉันพบตัวอย่างที่คล้ายกันในเอกสาร Vue อื่น ๆ : vuejs.org/v2/examples/index.html (เครื่องมือแก้ไข markdown)
MartinTeeVarga

5
วิธีแก้ไขปัญหาที่เสนอมีปัญหาเมื่อมีหลายอินสแตนซ์ขององค์ประกอบบนหน้า อธิบายปัญหาและวิธีแก้ไขที่นำเสนอที่นี่: forum.vuejs.org/t/issues-with-vuejs-component-and-debounce/7224/…
Valera

e.currentTarget ถูกเขียนทับเป็น null ด้วยวิธีนี้
ness-EE

1
อยากจะแนะนำให้เพิ่มv-model=your_input_variableการป้อนข้อมูลและใน dataVue ดังนั้นคุณไม่ต้องพึ่งพาe.targetแต่ใช้ Vue เพื่อให้คุณสามารถเข้าถึงthis.your_input_variableแทนe.target.value
DominikAngerer

1
สำหรับผู้ที่ใช้ ES6 เป็นสิ่งสำคัญที่จะต้องเน้นการใช้ฟังก์ชั่นที่ไม่ระบุชื่อที่นี่: หากคุณใช้ฟังก์ชั่นลูกศรคุณจะไม่สามารถเข้าถึงthisภายในฟังก์ชั่น
Polosson

68

การกำหนด debounce ในmethodsอาจเป็นปัญหาได้ ดังนั้นแทนที่จะเป็นเช่นนี้:

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

คุณอาจลอง:

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

มันจะกลายเป็นปัญหาหากคุณมีองค์ประกอบหลายอินสแตนซ์ - คล้ายกับวิธีที่dataควรเป็นฟังก์ชั่นที่ส่งกลับวัตถุ แต่ละอินสแตนซ์ต้องการฟังก์ชัน debounce ของตัวเองถ้าพวกเขาควรจะทำหน้าที่อิสระ

นี่คือตัวอย่างของปัญหา:

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
คุณช่วยอธิบายได้ไหมว่าเพราะเหตุใดการกำหนด debounce ในวิธีการจึงเป็นปัญหา
MartinTeeVarga

12
ดูตัวอย่างลิงก์ที่มีแนวโน้มที่จะ link-rot ดีกว่าที่จะอธิบายปัญหาในคำตอบ - มันจะทำให้มีค่ามากขึ้นสำหรับผู้อ่าน
MartinTeeVarga

ขอบคุณมากการจับคู่ฉันมีเวลาไม่ดีที่พยายามทำความเข้าใจว่าทำไมข้อมูลที่แสดงบนคอนโซลนั้นถูกต้อง แต่ไม่ได้ใช้กับแอป ...

@ sm4 เพราะแทนที่จะใช้อินสแตนซ์ที่ถูกเรียกคืนแบบแบ่งใช้เดียวกันสำหรับฟังก์ชั่นที่คุณต้องการมันจะสร้างมันใหม่ทุกครั้ง
Mike Sheward

1
เพียงเพิ่มลงในของคุณdata()แล้ว
Su-Au Hwang

45

อัพเดทในปี 2020

ตัวเลือกที่ 1: ใช้งานซ้ำได้ไม่เสียค่าใช้จ่าย

(แนะนำหากจำเป็นมากกว่าหนึ่งครั้งในโครงการของคุณ)

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


ตัวเลือกที่ 2: ในองค์ประกอบไม่มี deps

(แนะนำถ้าใช้เพียงครั้งเดียวหรือในโครงการขนาดเล็ก)

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
คุณเป็นฮีโร่ตัวจริง
Ashtonian

4
ฉันชอบตัวเลือกนี้เพราะฉันอาจไม่ต้องการแพ็กเกจ npm สำหรับรหัส 11 บรรทัด ....
Ben Winding

3
นี่ควรเป็นคำตอบที่ถูกทำเครื่องหมายได้ผลดีและใช้พื้นที่เกือบทั้งหมด ขอบคุณ!
Alexander Kludt

29

ง่ายมากโดยไม่พักเลย

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

4
เท่าที่ฉันรัก lodash นี่เป็นคำตอบที่ชัดเจนที่สุดสำหรับการ debounce ต่อท้าย ใช้งานได้ง่ายและเข้าใจได้ง่ายที่สุด
Michael Hays

2
ยังเป็นสิ่งที่ดีที่จะเพิ่มdestroyed() { clearInterval(this.timeout) }เพื่อไม่ให้หมดเวลาหลังจากถูกทำลาย
pikilon

13

ฉันมีปัญหาเดียวกันและนี่เป็นวิธีแก้ปัญหาที่ทำงานโดยไม่มีปลั๊กอิน

เนื่องจาก<input v-model="xxxx">เป็นเช่นเดียวกับ

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

(ที่มา)

ฉันคิดว่าฉันสามารถตั้งค่าฟังก์ชั่น debounce ในการกำหนดของ xxxx xxxx = $event.target.value

แบบนี้

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

วิธีการ:

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

1
หากฟิลด์อินพุตของคุณมีการ@input="update_something"ดำเนินการเช่นนี้ให้เรียกสิ่งนี้หลังจากนั้นthat.xxx = val that.update_something();
Neon22

1
ในส่วนวิธีการของฉันฉันใช้ไวยากรณ์ที่แตกต่างกันเล็กน้อยซึ่งทำงานให้ฉัน: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); },
23422

นี่ก็โอเคถ้าคุณมีอินสแตนซ์หนึ่งหรือไม่กี่ตัวที่คุณต้องการ debounce input อย่างไรก็ตามคุณจะรู้ได้อย่างรวดเร็วว่าคุณจะต้องย้ายสิ่งนี้ไปยังห้องสมุดหรือสิ่งที่คล้ายกันหากแอปเติบโตและจำเป็นต้องใช้ฟังก์ชั่นนี้ที่อื่น เก็บรหัสของคุณไว้
Coreus

5

โปรดทราบว่าฉันโพสต์คำตอบนี้ก่อนคำตอบที่ยอมรับ มันไม่ถูกต้อง เป็นเพียงขั้นตอนต่อไปจากโซลูชันในคำถาม ฉันได้แก้ไขคำถามที่ยอมรับแล้วเพื่อแสดงการใช้งานของผู้เขียนและการใช้งานขั้นสุดท้ายที่ฉันใช้


จากความคิดเห็นและเอกสารการโยกย้ายที่เชื่อมโยงฉันได้ทำการเปลี่ยนแปลงโค้ดบางประการ:

ในเทมเพลต:

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

ในสคริปต์:

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

และวิธีการตั้งค่าคีย์ตัวกรองยังคงเหมือนเดิม:

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

ดูเหมือนว่าจะมีการโทรที่น้อยกว่าหนึ่งรายการ (เพียงv-modelและไม่ใช่v-on:input)


การเรียกนี้จะไม่debounceInput()สองครั้งสำหรับการเปลี่ยนแปลงแต่ละครั้งหรือไม่ v-on:จะตรวจจับการเปลี่ยนแปลงการป้อนข้อมูลและการเรียก debounce และเนื่องจากรุ่นถูกผูกไว้ฟังก์ชั่นการค้นหาของ searchInput จะเรียกอีกอย่างว่าdebounceInput... ใช่มั้ย
mix3d

@ mix3d อย่าพิจารณาคำตอบนี้ มันเป็นเพียงการสอบสวนของฉันฉันไม่ต้องการที่จะใส่ในคำถาม คุณน่าจะเหมาะสม ตรวจสอบคำตอบที่ยอมรับ ถูกต้องและฉันแก้ไขให้ตรงกับคำถาม
MartinTeeVarga

ความผิดพลาดของฉัน ... ฉันไม่ได้ตระหนักว่าคุณได้ตอบคำถามของคุณแล้วฮ่า!
mix3d

5

หากคุณต้องการวิธีการที่เรียบง่ายอย่างนี้ฉันได้ทำหนึ่ง (เดิมแยกจาก vuejs-tips เพื่อสนับสนุน IE) ซึ่งมีให้ที่นี่: https://www.npmjs.com/package/v-debounce

การใช้งาน:

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

จากนั้นในองค์ประกอบของคุณ:

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

น่าจะเป็นอันนี้ควรเป็นทางออกที่ได้รับการยอมรับด้วยการโหวต 100+ OP ขอคำตอบที่กระทัดรัดแบบนี้
บาร์นีย์

1

ในกรณีที่คุณต้องใช้การหน่วงเวลาแบบไดนามิกกับdebounceฟังก์ชันของ 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) {
    // ...
  }
}

และแม่แบบ:

<template>
  //...

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

  //...
</template>

หมายเหตุ:ในตัวอย่างด้านบนฉันได้ทำตัวอย่างของอินพุตการค้นหาซึ่งสามารถเรียก API ด้วยการหน่วงเวลาที่กำหนดเองซึ่งมีให้props


1

แม้ว่าคำตอบทั้งหมดที่นี่ค่อนข้างถูกต้องแล้วหากใครก็ตามที่กำลังมองหาวิธีแก้ปัญหาอย่างรวดเร็วฉันมีคำสั่งสำหรับสิ่งนี้ https://www.npmjs.com/package/vue-lazy-input

มันใช้กับ @input และ v-model รองรับคอมโพเนนต์ที่กำหนดเองและองค์ประกอบ DOM, debounce และ 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

หากคุณกำลังใช้ Vue คุณสามารถใช้v.model.lazyแทนได้debounceแต่การจำไว้v.model.lazyจะไม่ทำงานเสมอเนื่องจาก Vue จะ จำกัด ไว้สำหรับส่วนประกอบที่กำหนดเอง

สำหรับองค์ประกอบที่กำหนดเองคุณควรใช้:valueพร้อมกับ@change.native

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


0

หากคุณสามารถย้ายการดำเนินการของฟังก์ชั่น debounce เป็นวิธีการเรียนบางอย่างคุณสามารถใช้มัณฑนากรจากutils-decorators lib ( npm install --save utils-decorators):

import {debounce} from 'utils-decorators';

class SomeService {

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

-1

เราสามารถทำได้โดยใช้รหัส JS สองสามบรรทัด:

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

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

ทางออกที่ง่าย! ทำงานได้สมบูรณ์แบบ! หวังว่าจะเป็นประโยชน์สำหรับพวกคุณ


2
แน่นอนว่า ... หากคุณต้องการสร้างมลพิษให้กับพื้นที่โลกและทำให้มันมีเพียง 1 องค์ประกอบเท่านั้นที่สามารถใช้ได้ในแต่ละครั้ง นี่คือคำตอบที่น่ากลัว
Hybrid web dev

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

vue-ทรัพย์สินมัณฑนากร


2
คุณช่วยเพิ่มข้อมูลเกี่ยวกับโซลูชันนี้ได้ไหม
rocha

2
โปรดอธิบายเพิ่มเติมอีกหน่อย นอกจากนี้โปรดทราบว่านี่เป็นเธรดเก่าที่มีคำตอบที่ดีดังนั้นคุณสามารถอธิบายได้ว่าโซลูชันของคุณเหมาะสมกับปัญหาได้อย่างไร
jpnadas

ช่วยได้มากขึ้นถ้าคุณให้คำอธิบายว่าทำไมนี่จึงเป็นโซลูชันที่ต้องการและอธิบายวิธีการทำงาน เราต้องการที่จะให้ความรู้ไม่เพียง แต่ให้รหัส
ชายดีบุก
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.