12

我有一个使用多个子组件的 Vue 组件。在这些子组件上,我有一个观察者来观察数据变化并处理这些变化。我想为此实现去抖动。

    watch: {
    data: {
      handler: function () {
        this.processData()
      },
      deep: true
    }
  },
  methods: {
    processData: debounce(function () {
      console.log(this.id)
    }, 250),

问题是 debounce 有效,因此它仅在最后一个子组件上执行。

我找到了一个接受额外 id debounceWithId的 debounce 函数的解决方案

但是有一个问题是,如果我指定这个函数如下:

  methods: {
    processData: debounceWithId(function () {
      console.log(this.id)
    }, 250, this.id),

最后一个 this.id 是未定义的。

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

4

2 回答 2

20

首先让我添加一个示例来复制您所描述的问题。

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在生成的 debounced 函数中添加了一个,以便您可以看到所有三个组件共享相同的函数。在这种情况下,该功能正在执行它的设计目的;它在经过一段时间后执行一次,这就是为什么只更新最后一个组件的原因。

为了避免这种行为,您需要为每个组件设置一个独特的去抖动功能。这里有两种方法可以做到这一点。

方法一

您可以使用占位符来初始化您的 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 提出这个建议。您可以在 中定义processData方法data。通常你不这样做是因为你不经常想要一个函数的多个副本,这就是methods组件定义部分存在的原因,但是在这种情况下,你需要为每个组件提供一个唯一的函数,你可以定义data 函数中的方法,因为该组件的每个实例都会调用 data 函数。

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>

于 2017-08-27T22:38:30.823 回答
0

另一个对我有用的解决方案$watch是在创建组件后使用 api 添加一个观察者,然后去抖功能将不会在组件之间共享

created () {
  this.$watch(
    'foo',
    debounce(function bar() {
      // do something
    },
    {}
  )
}
于 2020-08-14T06:30:44.377 回答