VueJS 2 在多个组件上去抖动
VueJS 2 debounce on multiple components
我有一个 Vue 组件,它使用了多个子组件。在这些子组件上,我有一个观察者来观察数据变化并处理这些变化。我想为此实现去抖动。
watch: {
data: {
handler: function () {
this.processData()
},
deep: true
}
},
methods: {
processData: debounce(function () {
console.log(this.id)
}, 250),
问题是 debounce 有效,所以它只在最后一个子组件上执行。
我找到了一个接受额外 id 的去抖功能解决方案
但是问题是,如果我按如下方式指定此函数:
methods: {
processData: debounceWithId(function () {
console.log(this.id)
}, 250, this.id),
最后一个 this.id 未定义。
在多个组件中使用 debounce 以便函数在每个组件上单独触发的正确方法是什么?
首先让我添加一个示例来复制您所描述的问题。
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>
基本上这里发生的事情是在编译组件时创建 debounced 函数,并且组件的每个实例共享 相同的 debounced 函数。 this
的上下文在每一个中都会不同,但它是相同的功能。我在生成的 debounced 函数中添加了一个 console.log
,以便您可以看到所有三个组件都共享相同的函数。在这种情况下,该功能正在执行其设计目的;它在经过一段时间后执行一次,这就是为什么只更新最后一个组件的原因。
要解决该问题,您需要为每个组件提供一个 unique 去抖功能。您可以通过以下两种方式做到这一点。
方法一
您可以使用占位符来初始化您的 processData 方法。
methods: {
processData(){}
}
然后,在创建的生命周期事件中,将processData方法改为debounced方法。
created(){
this.processData = debounce(function(){
console.log(this.id)
}, 250)
}
这将为每个组件提供独特的去抖功能,并且应该解决只有 last 组件正常工作的问题。
这是一个从上面的例子修改而来的例子。
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>
另一个对我有用的解决方案是使用 $watch
api 在创建组件后添加一个观察者,然后组件之间不会共享去抖动功能
created () {
this.$watch(
'foo',
debounce(function bar() {
// do something
},
{}
)
}
我有一个 Vue 组件,它使用了多个子组件。在这些子组件上,我有一个观察者来观察数据变化并处理这些变化。我想为此实现去抖动。
watch: {
data: {
handler: function () {
this.processData()
},
deep: true
}
},
methods: {
processData: debounce(function () {
console.log(this.id)
}, 250),
问题是 debounce 有效,所以它只在最后一个子组件上执行。
我找到了一个接受额外 id 的去抖功能解决方案
但是问题是,如果我按如下方式指定此函数:
methods: {
processData: debounceWithId(function () {
console.log(this.id)
}, 250, this.id),
最后一个 this.id 未定义。
在多个组件中使用 debounce 以便函数在每个组件上单独触发的正确方法是什么?
首先让我添加一个示例来复制您所描述的问题。
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>
基本上这里发生的事情是在编译组件时创建 debounced 函数,并且组件的每个实例共享 相同的 debounced 函数。 this
的上下文在每一个中都会不同,但它是相同的功能。我在生成的 debounced 函数中添加了一个 console.log
,以便您可以看到所有三个组件都共享相同的函数。在这种情况下,该功能正在执行其设计目的;它在经过一段时间后执行一次,这就是为什么只更新最后一个组件的原因。
要解决该问题,您需要为每个组件提供一个 unique 去抖功能。您可以通过以下两种方式做到这一点。
方法一
您可以使用占位符来初始化您的 processData 方法。
methods: {
processData(){}
}
然后,在创建的生命周期事件中,将processData方法改为debounced方法。
created(){
this.processData = debounce(function(){
console.log(this.id)
}, 250)
}
这将为每个组件提供独特的去抖功能,并且应该解决只有 last 组件正常工作的问题。
这是一个从上面的例子修改而来的例子。
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>
另一个对我有用的解决方案是使用 $watch
api 在创建组件后添加一个观察者,然后组件之间不会共享去抖动功能
created () {
this.$watch(
'foo',
debounce(function bar() {
// do something
},
{}
)
}