VueJS 2在多个组件上使用防抖函数

15

我有一个Vue组件,在它上面使用了多个子组件。在这些子组件上,我有一个监视器来监视数据更改并处理这些更改。我想为此实现防抖动。

    watch: {
    data: {
      handler: function () {
        this.processData()
      },
      deep: true
    }
  },
  methods: {
    processData: debounce(function () {
      console.log(this.id)
    }, 250),
问题在于防抖函数只在最后一个子组件上执行。

我找到了一个解决debounce函数的方案,它接受一个额外的ID:debounceWithId

然而,问题是如果我像下面这样指定这个函数:
  methods: {
    processData: debounceWithId(function () {
      console.log(this.id)
    }, 250, this.id),

最后的this.id未定义。

在多个组件中正确使用防抖函数的方法是什么,以便于函数在每个组件上单独触发?

2个回答

28

首先让我举一个复制你描述的问题的例子。

console.clear()

function debounce(func, wait, immediate) {
    var timeout;
    return function() {
        console.log("Called from component ", this._uid)
        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};

Vue.component("doesntwork",{
  props:["value"],
  template:`<div>Component #{{_uid}} Value: {{innerValue}}</div>`,
  data(){
    return {
      innerValue: this.value
    }
  },
  watch:{
    value(newVal){
      this.processData(newVal)
    }
  },
  methods:{
    processData: debounce(function(newVal){
      this.innerValue = newVal
    }, 1000)
  },
})


new Vue({
  el: "#app",
  data:{
    parentValue: null,
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://unpkg.com/vue@2.4.2"></script>
<div id="app">
  Type some text. Wait one second. Only the *last* component is updated.<br>
  <input type="text" v-model="parentValue">
  <doesntwork :value="parentValue"></doesntwork>
  <doesntwork :value="parentValue"></doesntwork>
  <doesntwork :value="parentValue"></doesntwork>
</div>

本质上这里正在发生的是当组件被编译时创建了防抖函数,每个组件实例共享同一个防抖函数。每个实例中的this上下文将不同,但是它是同一个函数。我在生成的防抖函数中添加了console.log,这样您就可以看到所有三个组件都在共享同一个函数。既然如此,该函数正在执行其设计的任务;在经过一段时间后,它仅会执行一次,这就是为什么只有最后一个组件被更新的原因。

要避免这种行为,需要为每个组件使用一个唯一的防抖函数。以下是两种方法。

方法一

您可以使用相当于占位符的内容初始化您的processData方法。

methods: {
  processData(){}
}

接下来,在创建的生命周期事件中,将processData方法更改为防抖函数(debounced)。

created(){
  this.processData = debounce(function(){
    console.log(this.id)
  }, 250)
}

这将为每个组件提供一个唯一的去抖动函数,并应该解决只有最后一个组件正常工作的问题。

以下是从上面示例修改的示例。

console.clear()

Vue.component("works",{
  props:["value"],
  template:`<div>Component #{{_uid}} Value: {{innerValue}}</div>`,
  data(){
    return {
      innerValue: this.value,
    }
  },
  watch:{
    value(newVal){
      this.processData(newVal)
    }
  },
  methods:{
    processData() {}
  },
  created(){
    this.processData = _.debounce(function(newVal){
      this.innerValue = newVal
    }, 1000)
  }
})

new Vue({
  el: "#app",
  data:{
    parentValue: null,
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://unpkg.com/vue@2.4.2"></script>
<div id="app">
  Type some text. Wait one second. <em>All</em> components are updated.<br>
  <input type="text" v-model="parentValue">
  <works :value="parentValue"></works>
  <works :value="parentValue"></works>
  <works :value="parentValue"></works>
</div>

方法二

感谢@RoyJ的建议。您可以在data中定义processData方法。通常情况下,您不会这样做,因为您很少需要多个函数的副本,这就是组件定义的methods部分存在的原因,但在像这样需要每个组件都有一个唯一函数的情况下,您可以在数据函数中定义该方法,因为数据函数会针对每个组件实例调用。

data(){
  return {
    innerValue: this.value,
    processData: _.debounce(function(newVal){
      this.innerValue = newVal
    }, 1000)
  }
},

以下是使用该方法的示例。

console.clear()

Vue.component("works",{
  props:["value"],
  template:`<div>Component #{{_uid}} Value: {{innerValue}}</div>`,
  data(){
    return {
      innerValue: this.value,
      processData: _.debounce(function(newVal){
        this.innerValue = newVal
      }, 1000)
    }
  },
  watch:{
    value(newVal){
      this.processData(newVal)
    }
  },
})

new Vue({
  el: "#app",
  data:{
    parentValue: null,
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://unpkg.com/vue@2.4.2"></script>
<div id="app">
  Type some text. Wait one second. <em>All</em> components are updated.<br>
  <input type="text" v-model="parentValue">
  <works :value="parentValue"></works>
  <works :value="parentValue"></works>
  <works :value="parentValue"></works>
</div>


@RoyJ 你有什么想法吗?在codepen中,我实际上并没有返回一个值,只是更新了一个数据属性。在那个不起作用的组件中,防抖方法只会触发一次。这让我有了一个恍然大悟的时刻。所有组件都使用相同的函数定义,其目的是在时间段结束后仅触发一次。 - Bert
1
就算价值不高,computed的工作方式也是一样的:如果你在setter中使用了debounce,那么所有实例都只会有一个防抖。这是一种比较微妙的意外行为,但如果你的代码DRY的话,它应该很少出现。在这个例子中,我们正在制作多个数据副本;防抖可能应该在父组件中完成,并作为prop传递。 - Roy J
2
此外,FWIW,您可以在“数据”部分中定义processData,它将为每个实例创建一次。 - Roy J
@RoyJ 我预计任何使用防抖动的东西(除了数据函数之外)都会遇到同样的问题。顺便说一下,你关于数据函数的想法真的很好;它确实是一个工厂函数,符合你昨晚的思路。这很不直观,但我赞同,它也应该解决问题。 - Bert
谢谢 - 现在它按照我预期的方式工作。我正在使用第二种方法,因为它看起来更简洁,并且可以减少代码行数。 - Raimundas Juška
你节省了我数小时的调试时间。 - GTCrais

1

对我而言,另一个可行的解决方案是使用$watch API,在组件创建后添加一个观察器,这样防抖函数就不会在组件之间共享。

created () {
  this.$watch(
    'foo',
    debounce(function bar() {
      // do something
    },
    {}
  )
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接