首先让我举一个复制你描述的问题的例子。
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>
computed
的工作方式也是一样的:如果你在setter中使用了debounce
,那么所有实例都只会有一个防抖。这是一种比较微妙的意外行为,但如果你的代码DRY的话,它应该很少出现。在这个例子中,我们正在制作多个数据副本;防抖可能应该在父组件中完成,并作为prop传递。 - Roy JprocessData
,它将为每个实例创建一次。 - Roy J