【问题标题】:How to implement debounce in Vue2?Vue2中如何实现去抖动?
【发布时间】:2019-12-31 11:56:43
【问题描述】:

我在 Vue 模板中有一个简单的输入框,我想或多或少地像这样使用 debounce:

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

但是debounce 属性已变为deprecated in Vue 2。建议只说:“使用 v-on:input + 3rd party debounce function”。

你是如何正确实现它的?

我尝试使用 lodashv-on:inputv-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 道具中使用。

【问题讨论】:

标签: vue.js vuejs2 debouncing


【解决方案1】:

我正在使用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)
}

【讨论】:

  • 谢谢。我在其他一些 Vue 文档中发现了一个类似的例子:vuejs.org/v2/examples/index.html(markdown 编辑器)
  • 建议的解决方案在页面上有多个组件实例时会出现问题。此处描述了问题并提出了解决方案:forum.vuejs.org/t/issues-with-vuejs-component-and-debounce/7224/…
  • e.currentTarget 以这种方式被覆盖为 null
  • 建议在输入和 vue data 中添加 v-model=your_input_variable。所以你不要依赖e.target,而是使用Vue,这样你就可以访问this.your_input_variable而不是e.target.value
  • 对于那些使用 ES6 的人来说,这里需要强调匿名函数的使用:如果你使用箭头函数,你将无法在函数内访问this
【解决方案2】:

选项 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:组件内,也没有依赖

- 建议使用一次或在小型项目中使用

/Component.vue

<template>
    <input type="text" v-model="input" />
</template>

<script>
  export default {
    data: {
        timeout: null,
        debouncedInput: ''
    },
    computed: {
     input: {
        get() {
          return this.debouncedInput
        },
        set(val) {
          if (this.timeout) clearTimeout(this.timeout)
          this.timeout = setTimeout(() => {
            this.debouncedInput = val
          }, 300)
        }
      }
    }
  }
</script>

Codepen

【讨论】:

  • 你才是真正的英雄
  • 我更喜欢这个选项,因为我可能不需要 11 行代码的 npm 包......
  • 这应该是标记的答案,这非常有效,几乎不占用空间。谢谢!
  • 嗨,可以添加一个 TypeScript 版本的助手吗?
  • 在实施第一个选项时是否还有其他人遇到开玩笑的错误? [Vue warn]: Error in callback for watcher "input": "TypeError: Cannot read property 'call' of undefined"
【解决方案3】:

methods 中分配去抖动可能会很麻烦。所以不要这样:

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

你可以试试:

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

如果您有一个组件的多个实例,这将成为一个问题 - 类似于 data 应该是一个返回对象的函数的方式。如果每个实例应该独立运行,则它们需要自己的去抖动功能。

这是一个问题的例子:

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>

【讨论】:

  • 您能解释一下为什么在方法中分配去抖动会很麻烦吗?
  • 查看示例链接容易发生链接腐烂。最好在答案中解释问题 - 这会使它对读者更有价值。
  • 非常感谢你,我很难理解为什么控制台上显示的数据是正确的但没有应用到应用程序上...
  • 然后将其添加到您的data()
  • @Hybridwebdev 我认为他是从 Vue 论坛上的 Linus Borg 的回答中得到的,所以我会说这是正确的解决方案 forum.vuejs.org/t/…
【解决方案4】:

没有lodash很简单

handleScroll: function() {
  if (this.timeout) 
    clearTimeout(this.timeout); 

  this.timeout = setTimeout(() => {
    // your action
  }, 200); // delay
}

【讨论】:

  • 尽管我很喜欢 lodash,但这显然是尾随去抖动的最佳答案。最容易实施和理解。
  • 添加destroyed() { clearInterval(this.timeout) } 也是一件好事,以便在销毁后不会超时。
  • 在所有解决方案中,这是唯一一个可靠工作的解决方案。
  • 简单、高效、棒!
  • 我不确定如何在输入字段上的文本更改时使用它。有人可以举个例子吗?
【解决方案5】:

我遇到了同样的问题,这是一个无需插件即可工作的解决方案。

由于&lt;input v-model="xxxx"&gt;

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

(source)

我想我可以为xxxx = $event.target.value中的xxxx分配设置一个去抖功能

喜欢这个

<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);
},

【讨论】:

  • 如果您的输入字段也有@input="update_something" 操作,那么在that.xxx = val that.update_something(); 之后调用它
  • 在我的方法部分,我使用了一种对我有用的稍微不同的语法: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); },
  • 如果您有一个或很少的实例需要去抖动输入,这没关系。但是,如果应用程序增长并且其他地方需要此功能,您很快就会意识到您需要将其移动到库或类似的库中。保持代码干燥。
【解决方案6】:

如果您需要一种非常简约的方法,我制作了一个(最初从 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 要求像这样的紧凑解决方案,它很好地解耦了去抖动逻辑。
  • 如果你玩数组会很困难,因为这种方式取决于静态数据
【解决方案7】:

请注意,我在接受的答案之前发布了此答案。它不是 正确的。这只是从解决方案向前迈出的一步 题。我已经编辑了接受的问题,以显示作者的实现和我使用的最终实现。


基于 cmets 和 linked migration document,我对代码做了一些改动:

在模板中:

<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 的 watch 函数也会调用 debounceInput... 对吧?
  • @mix3d 不要考虑这个答案。这只是我的调查,我不想提出这个问题。你很可能是对的。检查接受的答案。这是正确的,我对其进行了编辑以匹配问题。
  • 我的错误......我没有意识到你已经回答了你自己的问题,哈!
【解决方案8】:

如果您需要使用 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) {
    // ...
  }
}

还有模板:

<template>
  //...

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

  //...
</template>

注意:在上面的例子中,我做了一个搜索输入的例子,它可以使用props中提供的自定义延迟调用API

【讨论】:

    【解决方案9】:

    虽然这里几乎所有的答案都是正确的,但如果有人正在寻找快速解决方案,我对此有一个指令。 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>

    【讨论】:

      【解决方案10】:

      要创建去抖动方法,您可以使用计算方法,这样它们就不会在组件的多个实例之间共享:

      <template>
        <input @input="handleInputDebounced">
      <template>
      
      <script>
      import debounce from 'lodash.debouce';
      
      export default {
        props: {
          timeout: {
            type: Number,
            default: 200,
          },
        },
        methods: {
          handleInput(event) {
            // input handling logic
          },
        },
        computed: {
          handleInputDebounced() {
            return debounce(this.handleInput, this.timeout);
          },
        },
      }
      </script>
      

      您也可以使其与不受控制的v-model 一起工作:

      <template>
        <input v-model="debouncedModel">
      <template>
      
      <script>
      import debounce from 'lodash.debouce';
      
      export default {
        props: {
          value: String,
          timeout: {
            type: Number,
            default: 200,
          },
        },
        methods: {
          updateValue(value) {
            this.$emit('input', value);
          },
        },
        computed: {
          updateValueDebounced() {
            return debounce(this.updateValue, this.timeout);
          },
          debouncedModel: {
            get() { return this.value; },
            set(value) { this.updateValueDebounced(value); }
          },
        },
      }
      </script>
      

      【讨论】:

        【解决方案11】:

        如果您使用 Vue,您也可以使用 v.model.lazy 代替 debounce,但请记住 v.model.lazy 并不总是有效,因为 Vue 将其限制为自定义组件。

        对于自定义组件,您应该使用 :value@change.native

        &lt;b-input :value="data" @change.native="data = $event.target.value" &gt;&lt;/b-input&gt;

        【讨论】:

          【解决方案12】:

          1 使用箭头函数的短版本,默认延迟

          file: debounce.js in ex: ( import debounce from '../../utils/debounce' )

          export default function (callback, delay=300) {
              let timeout = null
              return (...args) => {
                  clearTimeout(timeout)
                  const context = this
                  timeout = setTimeout(() => callback.apply(context, args), delay)
              }
          }
          

          2 混合选项

          文件:debounceMixin.js

          export default {
            methods: {
              debounce(func, delay=300) {
                let debounceTimer;
                return function() {
                 // console.log("debouncing call..");
                  const context = this;
                  const args = arguments;
                  clearTimeout(debounceTimer);
                  debounceTimer = setTimeout(() => func.apply(context, args), delay);
                  // console.log("..done");
                };
              }
            }
          };
          

          在 vueComponent 中使用:

          <script>
            import debounceMixin from "../mixins/debounceMixin";
            export default {
             mixins: [debounceMixin],
                  data() {
                      return {
                          isUserIdValid: false,
                      };
                  },
                  mounted() {
                  this.isUserIdValid = this.debounce(this.checkUserIdValid, 1000);
                  },
              methods: {
                  isUserIdValid(id){
                  // logic
                  }
            }
          </script>
          

          另一个选项,示例

          Vue search input debounce

          【讨论】:

            【解决方案13】:

            我只需很少的实现就可以使用 debounce。

            我正在使用带有 boostrap-vue 的 Vue 2.6.14:

            将此 pkg 添加到您的 package.json:https://www.npmjs.com/package/debounce

            将此添加到 main.js:

            import { debounce } from "debounce";
            Vue.use(debounce);
            

            在我的组件中我有这个输入:

                      <b-form-input
                        debounce="600"
                        @update="search()"
                        trim
                        id="username"
                        v-model="form.userName"
                        type="text"
                        placeholder="Enter username"
                        required
                      >
                      </b-form-input>
            

            它所做的只是调用 search() 方法,而 search 方法使用 form.userName 来执行搜索。

            【讨论】:

              【解决方案14】:

              如果您可以将 debounce 函数的执行转移到某个类方法中,您可以使用来自 utils-decorators 库 (npm install --save utils-decorators) 的装饰器:

              import {debounce} from 'utils-decorators';
              
              class SomeService {
              
                @debounce(500)
                getData(params) {
                }
              }
              

              【讨论】:

                【解决方案15】:
                 public debChannel = debounce((key) => this.remoteMethodChannelName(key), 200)
                

                vue-property-decorator

                【讨论】:

                • 能否请您添加有关此解决方案的更多信息?
                • 请详细说明一下。另外,请注意,这是一个老话题,答案很明确,所以你能澄清一下你的解决方案如何更适合这个问题吗?
                • 如果您提供解释为什么这是首选解决方案并解释它是如何工作的,它会更有帮助。我们想要教育,而不仅仅是提供代码。
                猜你喜欢
                • 2017-07-01
                • 2013-11-17
                • 1970-01-01
                • 2022-06-16
                • 2021-09-08
                • 1970-01-01
                • 1970-01-01
                • 2021-09-20
                • 2017-01-30
                相关资源
                最近更新 更多