如何在Vue中对回调进行退避和节制
侦听经常发生的事件,如用户在输入框中打字、窗口调整大小、滚动、交叉观察者事件等,需要采取预防措施。
这些事件可能会经常发生,例如每秒几次,因此在每个事件上调用像获取请求这样的操作并不是一个明智的做法。
你可以做的是放慢事件处理程序的执行速度。这样的摊销技术是debouncing和throttling。
在这篇文章中,你会发现如何在Vue组件中对观察者和事件处理程序进行去重和节流。
1.放弃一个观察者
让我们从一个简单的组件开始,你的任务是把用户在文本输入中引入的值记录到控制台。
vue
<template>
<input v-model="value" type="text" />
<p>{{ value }}</p>
</template>
<script>
export default {
data() {
return {
value: "",
};
},
watch: {
value(newValue, oldValue) {
console.log("Value changed: ", newValue);
}
}
};
</script>
打开Demo,在输入框中输入几个字符。每次你输入时,该值都会被记录到控制台。
记录是通过value 数据属性的观察者实现的。而如果你想使用value 作为观察者回调里面的GET参数来执行一个获取请求,你就不会想这么频繁地启动获取请求。
让我们把输入值记录到控制台的过程去掉。我们的想法是创建一个去抖函数,然后在观察者内部调用该函数。
我使用了一个来自'lodash.debounce' 的debounce实现,但你可以使用你喜欢的任何实现。
让我们用去抖功能来更新这个组件。
vue
<template>
<input v-model="value" type="text" />
<p>{{ value }}</p>
</template>
<script>
import debounce from "lodash.debounce";
export default {
data() {
return {
value: "",
};
},
watch: {
value(...args) {
this.debouncedWatch(...args);
},
},
created() {
this.debouncedWatch = debounce((newValue, oldValue) => {
console.log('New value:', newValue);
}, 500);
},
beforeUnmount() {
this.debouncedWatch.cancel();
},
};
</script>
如果你打开这个演示,你会注意到从用户的角度看没有什么变化:你仍然可以像前面的例子那样引入字符。
但有一个变化:只有在上次输入后500ms ,该组件才会将新值记录到控制台。这就是去势的作用。
观察者的退避是通过3个简单的步骤实现的:
-
在
created()钩子里面,创建了被取消的回调,并分配给实例的一个属性:this.debouncedWatch = debounce(..., 500)。 -
在观察回调
watch.value() { ... }里面,this.debouncedWatch()被调用,并带有正确的参数。 -
最后,
beforeUnmount()钩子在卸载组件之前取消了this.debouncedWatch.cancel()任何待执行的去中介函数。
以同样的方式,你可以去看任何数据属性。然后,你就可以安全地在debounced callback里面执行相对较重的操作,如数据获取、昂贵的DOM操作,等等。
2.放弃一个事件处理程序
上面的部分已经展示了如何让观察者退出,但是常规的事件处理程序呢?
让我们再次使用用户在输入框中输入数据的例子,但这次要给输入附加一个事件处理程序。
像往常一样,如果你不执行任何摊销,当用户输入时,改变的值会准确地记录到控制台。
vue
<template>
<input v-on:input="handler" type="text" />
</template>
<script>
export default {
methods: {
handler(event) {
console.log('New value:', event.target.value);
}
}
};
</script>
打开演示,在输入框中输入几个字符。看一下控制台:你会注意到,每次输入时控制台都会更新。
同样,如果你想对输入值进行一些相对较重的操作,例如执行一个获取请求,这并不总是很方便。
脱离事件处理程序可以按以下方式实现。
vue
<template>
<input v-on:input="debouncedHandler" type="text" />
</template>
<script>
import debounce from "lodash.debounce";
export default {
created() {
this.debouncedHandler = debounce(event => {
console.log('New value:', event.target.value);
}, 500);
},
beforeUnmount() {
this.debouncedHandler.cancel();
}
};
</script>
打开演示,输入几个字符。只有当500ms ,该组件才会将新的数值记录到控制台。再一次,退避是有效的。
在3个简单的步骤中实现了事件处理程序的脱跳:
-
在
created()钩子中,在实例创建后,将退避的回调分配给this.debouncedHandlerdebounce(event => {...}, 500)。 -
模板内的输入字段有
v-on:input,分配给debouncedHandler。
html
<input v-on:input="debouncedHandler" type="text" />
- 最后,在组件实例应该卸载的时候,在
beforeUnmount()钩子里面调用this.debouncedHandler.cancel(),以取消任何未决的函数调用。
顺便说一下,这些例子使用的是debouncing技术。然而,同样的方法也可以用来创建节流的函数。
3.一句提醒
你可能会想:为什么不直接在组件选项上把跳出的函数做成一个方法,然后在模板内把这个方法作为一个事件处理程序?
javascript
// ...
methods: {
// Why not?
debouncedHandler: debounce(function () { ... }}, 500)
}
// ...
这将是一个比在实例上创建debounced函数作为属性更容易的方法。
比如说:
vue
<template>
<input v-on:input="debouncedHandler" type="text" />
</template>
<script>
import debounce from "lodash.debounce";
export default {
methods: {
// Don't do this!
debouncedHandler: debounce(function(event) {
console.log('New value:', event.target.value);
}, 500)
}
};
</script>
这次你没有在created() 钩子里面创建一个debounced callback,而是把debounced callback分配给了methods.debouncedHandler 。
如果你试一下这个演示,它就能工作了!
问题是,使用export default { ... } 从组件中导出的选项对象,包括方法,将被该组件的所有实例重用。
如果网页上有2个或2个以上的组件实例,那么所有的组件都会使用相同的debounced函数methods.debouncedHandler ,而debouncing可能会出现故障。
4.总结
在Vue中,你可以很容易地将debouncing和节流技术应用于观察者和事件处理程序的回调。
主要的方法是在created() 钩子中,把去势或节流的回调作为实例的一个属性来创建。