如何在 Vue2 中实现去抖动?
How to implement debounce in Vue2?
我在 Vue 模板中有一个简单的输入框,我想像这样或多或少地使用 debounce:
<input type="text" v-model="filterKey" debounce="500">
然而 debounce
属性 已经 deprecated in Vue 2。推荐只说:“使用 v-on:input + 3rd party debounce function”。
你是如何正确实施的?
我尝试使用 lodash、v-on:input 和 v-model,但我想知道是否可以不使用额外变量。
在模板中:
<input type="text" v-on:input="debounceInput" v-model="searchInput">
在脚本中:
data: function () {
return {
searchInput: '',
filterKey: ''
}
},
methods: {
debounceInput: _.debounce(function () {
this.filterKey = this.searchInput;
}, 500)
}
稍后在 computed
道具中使用过滤器键。
Please note that I posted this answer before the accepted answer. It's not
correct. It's just a step forward from the solution in the
question. I have edited the accepted question to show both the author's implementation and the final implementation I had used.
根据评论和 linked migration document,我对代码做了一些修改:
在模板中:
<input type="text" v-on:input="debounceInput" v-model="searchInput">
在脚本中:
watch: {
searchInput: function () {
this.debounceInput();
}
},
设置过滤键的方法不变:
methods: {
debounceInput: _.debounce(function () {
this.filterKey = this.searchInput;
}, 500)
}
这看起来好像少了一个调用(只有 v-model
,而不是 v-on:input
)。
我正在使用 debounce NPM 包并像这样实现:
<input @input="debounceInput">
methods: {
debounceInput: debounce(function (e) {
this.$store.dispatch('updateInput', e.target.value)
}, config.debouncers.default)
}
使用 lodash 和问题中的示例,实现如下所示:
<input v-on:input="debounceInput">
methods: {
debounceInput: _.debounce(function (e) {
this.filterKey = e.target.value;
}, 500)
}
在 methods
中分配去抖可能很麻烦。所以不是这个:
// Bad
methods: {
foo: _.debounce(function(){}, 1000)
}
你可以试试:
// Good
created () {
this.foo = _.debounce(function(){}, 1000);
}
如果您有一个组件的多个实例,这将成为一个问题 - 类似于 data
应该是一个函数而 returns 一个对象的方式。如果每个实例都应该独立运行,则它们需要自己的去抖功能。
这是一个问题示例:
Vue.component('counter', {
template: '<div>{{ i }}</div>',
data: function(){
return { i: 0 };
},
methods: {
// DON'T DO THIS
increment: _.debounce(function(){
this.i += 1;
}, 1000)
}
});
new Vue({
el: '#app',
mounted () {
this.$refs.counter1.increment();
this.$refs.counter2.increment();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
<div id="app">
<div>Both should change from 0 to 1:</div>
<counter ref="counter1"></counter>
<counter ref="counter2"></counter>
</div>
如果你需要一个非常简约的方法,我做了一个(最初从 vuejs-tips 派生出来也支持 IE),可以在这里找到:https://www.npmjs.com/package/v-debounce
用法:
<input v-model.lazy="term" v-debounce="delay" placeholder="Search for something" />
然后在你的组件中:
<script>
export default {
name: 'example',
data () {
return {
delay: 1000,
term: '',
}
},
watch: {
term () {
// Do something with search term after it debounced
console.log(`Search term changed to ${this.term}`)
}
},
directives: {
debounce
}
}
</script>
我遇到了同样的问题,这里有一个无需插件的解决方案。
因为<input v-model="xxxx">
与
完全一样
<input
v-bind:value="xxxx"
v-on:input="xxxx = $event.target.value"
>
(source)
我想我可以在 xxxx = $event.target.value
中对 xxxx 的赋值设置去抖功能
像这样
<input
v-bind:value="xxxx"
v-on:input="debounceSearch($event.target.value)"
>
方法:
debounceSearch(val){
if(search_timeout) clearTimeout(search_timeout);
var that=this;
search_timeout = setTimeout(function() {
that.xxxx = val;
}, 400);
},
选项 1:可重复使用,无 deps
- 如果在您的项目中多次需要,推荐使用
/helpers.js
export function debounce (fn, delay) {
var timeoutID = null
return function () {
clearTimeout(timeoutID)
var args = arguments
var that = this
timeoutID = setTimeout(function () {
fn.apply(that, args)
}, delay)
}
}
/Component.vue
<script>
import {debounce} from './helpers'
export default {
data () {
return {
input: '',
debouncedInput: ''
}
},
watch: {
input: debounce(function (newVal) {
this.debouncedInput = newVal
}, 500)
}
}
</script>
选项 2:组件内,也没有 deps
- 如果使用一次或在小项目中推荐
/Component.vue
<template>
<input type="text" v-model="input" />
</template>
<script>
export default {
data: {
timeout: null,
debouncedInput: ''
},
computed: {
input: {
get() {
return this.debouncedInput
},
set(val) {
if (this.timeout) clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.debouncedInput = val
}, 300)
}
}
}
}
</script>
没有 lodash 非常简单
handleScroll: function() {
if (this.timeout)
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
// your action
}, 200); // delay
}
如果您使用的是 Vue,您也可以使用 v.model.lazy
而不是 debounce
,但请记住 v.model.lazy
并不总是有效,因为 Vue 将其限制在自定义组件中。
对于自定义组件,您应该使用 :value
以及 @change.native
<b-input :value="data" @change.native="data = $event.target.value" ></b-input>
如果您需要使用 lodash 的 debounce
函数应用动态延迟:
props: {
delay: String
},
data: () => ({
search: null
}),
created () {
this.valueChanged = debounce(function (event) {
// Here you have access to `this`
this.makeAPIrequest(event.target.value)
}.bind(this), this.delay)
},
methods: {
makeAPIrequest (newVal) {
// ...
}
}
和模板:
<template>
//...
<input type="text" v-model="search" @input="valueChanged" />
//...
</template>
注意: 在上面的例子中,我做了一个搜索输入的例子,它可以调用 API 和 props
中提供的自定义延迟
虽然这里几乎所有答案都已经正确,但如果有人正在寻找快速解决方案,我有一个指令。
https://www.npmjs.com/package/vue-lazy-input
适用于@input和v-model,支持自定义组件和DOM元素,去抖和节流。
Vue.use(VueLazyInput)
new Vue({
el: '#app',
data() {
return {
val: 42
}
},
methods:{
onLazyInput(e){
console.log(e.target.value)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/lodash/lodash.min.js"></script><!-- dependency -->
<script src="https://unpkg.com/vue-lazy-input@latest"></script>
<div id="app">
<input type="range" v-model="val" @input="onLazyInput" v-lazy-input /> {{val}}
</div>
如果您可以将去抖功能的执行移动到某些 class 方法中,您可以使用 utils-decorators 库中的装饰器 (npm install --save utils-decorators
):
import {debounce} from 'utils-decorators';
class SomeService {
@debounce(500)
getData(params) {
}
}
public debChannel = debounce((key) => this.remoteMethodChannelName(key), 200)
vue-属性-装饰器
要创建去抖动方法,您可以使用计算,这样它们就不会在组件的多个实例之间共享:
<template>
<input @input="handleInputDebounced">
<template>
<script>
import debounce from 'lodash.debouce';
export default {
props: {
timeout: {
type: Number,
default: 200,
},
},
methods: {
handleInput(event) {
// input handling logic
},
},
computed: {
handleInputDebounced() {
return debounce(this.handleInput, this.timeout);
},
},
}
</script>
您也可以让它与不受控制的 v-model
一起工作:
<template>
<input v-model="debouncedModel">
<template>
<script>
import debounce from 'lodash.debouce';
export default {
props: {
value: String,
timeout: {
type: Number,
default: 200,
},
},
methods: {
updateValue(value) {
this.$emit('input', value);
},
},
computed: {
updateValueDebounced() {
return debounce(this.updateValue, this.timeout);
},
debouncedModel: {
get() { return this.value; },
set(value) { this.updateValueDebounced(value); }
},
},
}
</script>
1 短版使用箭头函数,默认延迟值
文件:debounce.js in ex: ( import debounce from '../../utils/debounce' )
export default function (callback, delay=300) {
let timeout = null
return (...args) => {
clearTimeout(timeout)
const context = this
timeout = setTimeout(() => callback.apply(context, args), delay)
}
}
2 混合选项
文件:debounceMixin.js
export default {
methods: {
debounce(func, delay=300) {
let debounceTimer;
return function() {
// console.log("debouncing call..");
const context = this;
const args = arguments;
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => func.apply(context, args), delay);
// console.log("..done");
};
}
}
};
在vueComponent中使用:
<script>
import debounceMixin from "../mixins/debounceMixin";
export default {
mixins: [debounceMixin],
data() {
return {
isUserIdValid: false,
};
},
mounted() {
this.isUserIdValid = this.debounce(this.checkUserIdValid, 1000);
},
methods: {
isUserIdValid(id){
// logic
}
}
</script>
另一个选项,例子
我能够使用很少的实现来使用去抖动。
我正在使用带有 boostrap-vue 的 Vue 2.6.14:
将此 pkg 添加到您的 package.json:https://www.npmjs.com/package/debounce
将此添加到 main.js:
import { debounce } from "debounce";
Vue.use(debounce);
在我的组件中我有这样的输入:
<b-form-input
debounce="600"
@update="search()"
trim
id="username"
v-model="form.userName"
type="text"
placeholder="Enter username"
required
>
</b-form-input>
它所做的只是调用 search() 方法,搜索方法使用 form.userName 执行搜索。
我在 Vue 模板中有一个简单的输入框,我想像这样或多或少地使用 debounce:
<input type="text" v-model="filterKey" debounce="500">
然而 debounce
属性 已经 deprecated in Vue 2。推荐只说:“使用 v-on:input + 3rd party debounce function”。
你是如何正确实施的?
我尝试使用 lodash、v-on:input 和 v-model,但我想知道是否可以不使用额外变量。
在模板中:
<input type="text" v-on:input="debounceInput" v-model="searchInput">
在脚本中:
data: function () {
return {
searchInput: '',
filterKey: ''
}
},
methods: {
debounceInput: _.debounce(function () {
this.filterKey = this.searchInput;
}, 500)
}
稍后在 computed
道具中使用过滤器键。
Please note that I posted this answer before the accepted answer. It's not correct. It's just a step forward from the solution in the question. I have edited the accepted question to show both the author's implementation and the final implementation I had used.
根据评论和 linked migration document,我对代码做了一些修改:
在模板中:
<input type="text" v-on:input="debounceInput" v-model="searchInput">
在脚本中:
watch: {
searchInput: function () {
this.debounceInput();
}
},
设置过滤键的方法不变:
methods: {
debounceInput: _.debounce(function () {
this.filterKey = this.searchInput;
}, 500)
}
这看起来好像少了一个调用(只有 v-model
,而不是 v-on:input
)。
我正在使用 debounce NPM 包并像这样实现:
<input @input="debounceInput">
methods: {
debounceInput: debounce(function (e) {
this.$store.dispatch('updateInput', e.target.value)
}, config.debouncers.default)
}
使用 lodash 和问题中的示例,实现如下所示:
<input v-on:input="debounceInput">
methods: {
debounceInput: _.debounce(function (e) {
this.filterKey = e.target.value;
}, 500)
}
在 methods
中分配去抖可能很麻烦。所以不是这个:
// Bad
methods: {
foo: _.debounce(function(){}, 1000)
}
你可以试试:
// Good
created () {
this.foo = _.debounce(function(){}, 1000);
}
如果您有一个组件的多个实例,这将成为一个问题 - 类似于 data
应该是一个函数而 returns 一个对象的方式。如果每个实例都应该独立运行,则它们需要自己的去抖功能。
这是一个问题示例:
Vue.component('counter', {
template: '<div>{{ i }}</div>',
data: function(){
return { i: 0 };
},
methods: {
// DON'T DO THIS
increment: _.debounce(function(){
this.i += 1;
}, 1000)
}
});
new Vue({
el: '#app',
mounted () {
this.$refs.counter1.increment();
this.$refs.counter2.increment();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
<div id="app">
<div>Both should change from 0 to 1:</div>
<counter ref="counter1"></counter>
<counter ref="counter2"></counter>
</div>
如果你需要一个非常简约的方法,我做了一个(最初从 vuejs-tips 派生出来也支持 IE),可以在这里找到:https://www.npmjs.com/package/v-debounce
用法:
<input v-model.lazy="term" v-debounce="delay" placeholder="Search for something" />
然后在你的组件中:
<script>
export default {
name: 'example',
data () {
return {
delay: 1000,
term: '',
}
},
watch: {
term () {
// Do something with search term after it debounced
console.log(`Search term changed to ${this.term}`)
}
},
directives: {
debounce
}
}
</script>
我遇到了同样的问题,这里有一个无需插件的解决方案。
因为<input v-model="xxxx">
与
<input
v-bind:value="xxxx"
v-on:input="xxxx = $event.target.value"
>
(source)
我想我可以在 xxxx = $event.target.value
像这样
<input
v-bind:value="xxxx"
v-on:input="debounceSearch($event.target.value)"
>
方法:
debounceSearch(val){
if(search_timeout) clearTimeout(search_timeout);
var that=this;
search_timeout = setTimeout(function() {
that.xxxx = val;
}, 400);
},
选项 1:可重复使用,无 deps
- 如果在您的项目中多次需要,推荐使用
/helpers.js
export function debounce (fn, delay) {
var timeoutID = null
return function () {
clearTimeout(timeoutID)
var args = arguments
var that = this
timeoutID = setTimeout(function () {
fn.apply(that, args)
}, delay)
}
}
/Component.vue
<script>
import {debounce} from './helpers'
export default {
data () {
return {
input: '',
debouncedInput: ''
}
},
watch: {
input: debounce(function (newVal) {
this.debouncedInput = newVal
}, 500)
}
}
</script>
选项 2:组件内,也没有 deps
- 如果使用一次或在小项目中推荐
/Component.vue
<template>
<input type="text" v-model="input" />
</template>
<script>
export default {
data: {
timeout: null,
debouncedInput: ''
},
computed: {
input: {
get() {
return this.debouncedInput
},
set(val) {
if (this.timeout) clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.debouncedInput = val
}, 300)
}
}
}
}
</script>
没有 lodash 非常简单
handleScroll: function() {
if (this.timeout)
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
// your action
}, 200); // delay
}
如果您使用的是 Vue,您也可以使用 v.model.lazy
而不是 debounce
,但请记住 v.model.lazy
并不总是有效,因为 Vue 将其限制在自定义组件中。
对于自定义组件,您应该使用 :value
以及 @change.native
<b-input :value="data" @change.native="data = $event.target.value" ></b-input>
如果您需要使用 lodash 的 debounce
函数应用动态延迟:
props: {
delay: String
},
data: () => ({
search: null
}),
created () {
this.valueChanged = debounce(function (event) {
// Here you have access to `this`
this.makeAPIrequest(event.target.value)
}.bind(this), this.delay)
},
methods: {
makeAPIrequest (newVal) {
// ...
}
}
和模板:
<template>
//...
<input type="text" v-model="search" @input="valueChanged" />
//...
</template>
注意: 在上面的例子中,我做了一个搜索输入的例子,它可以调用 API 和 props
中提供的自定义延迟
虽然这里几乎所有答案都已经正确,但如果有人正在寻找快速解决方案,我有一个指令。 https://www.npmjs.com/package/vue-lazy-input
适用于@input和v-model,支持自定义组件和DOM元素,去抖和节流。
Vue.use(VueLazyInput)
new Vue({
el: '#app',
data() {
return {
val: 42
}
},
methods:{
onLazyInput(e){
console.log(e.target.value)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/lodash/lodash.min.js"></script><!-- dependency -->
<script src="https://unpkg.com/vue-lazy-input@latest"></script>
<div id="app">
<input type="range" v-model="val" @input="onLazyInput" v-lazy-input /> {{val}}
</div>
如果您可以将去抖功能的执行移动到某些 class 方法中,您可以使用 utils-decorators 库中的装饰器 (npm install --save utils-decorators
):
import {debounce} from 'utils-decorators';
class SomeService {
@debounce(500)
getData(params) {
}
}
public debChannel = debounce((key) => this.remoteMethodChannelName(key), 200)
vue-属性-装饰器
要创建去抖动方法,您可以使用计算,这样它们就不会在组件的多个实例之间共享:
<template>
<input @input="handleInputDebounced">
<template>
<script>
import debounce from 'lodash.debouce';
export default {
props: {
timeout: {
type: Number,
default: 200,
},
},
methods: {
handleInput(event) {
// input handling logic
},
},
computed: {
handleInputDebounced() {
return debounce(this.handleInput, this.timeout);
},
},
}
</script>
您也可以让它与不受控制的 v-model
一起工作:
<template>
<input v-model="debouncedModel">
<template>
<script>
import debounce from 'lodash.debouce';
export default {
props: {
value: String,
timeout: {
type: Number,
default: 200,
},
},
methods: {
updateValue(value) {
this.$emit('input', value);
},
},
computed: {
updateValueDebounced() {
return debounce(this.updateValue, this.timeout);
},
debouncedModel: {
get() { return this.value; },
set(value) { this.updateValueDebounced(value); }
},
},
}
</script>
1 短版使用箭头函数,默认延迟值
文件:debounce.js in ex: ( import debounce from '../../utils/debounce' )
export default function (callback, delay=300) {
let timeout = null
return (...args) => {
clearTimeout(timeout)
const context = this
timeout = setTimeout(() => callback.apply(context, args), delay)
}
}
2 混合选项
文件:debounceMixin.js
export default {
methods: {
debounce(func, delay=300) {
let debounceTimer;
return function() {
// console.log("debouncing call..");
const context = this;
const args = arguments;
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => func.apply(context, args), delay);
// console.log("..done");
};
}
}
};
在vueComponent中使用:
<script>
import debounceMixin from "../mixins/debounceMixin";
export default {
mixins: [debounceMixin],
data() {
return {
isUserIdValid: false,
};
},
mounted() {
this.isUserIdValid = this.debounce(this.checkUserIdValid, 1000);
},
methods: {
isUserIdValid(id){
// logic
}
}
</script>
另一个选项,例子
我能够使用很少的实现来使用去抖动。
我正在使用带有 boostrap-vue 的 Vue 2.6.14:
将此 pkg 添加到您的 package.json:https://www.npmjs.com/package/debounce
将此添加到 main.js:
import { debounce } from "debounce";
Vue.use(debounce);
在我的组件中我有这样的输入:
<b-form-input
debounce="600"
@update="search()"
trim
id="username"
v-model="form.userName"
type="text"
placeholder="Enter username"
required
>
</b-form-input>
它所做的只是调用 search() 方法,搜索方法使用 form.userName 执行搜索。