在开发中我们经常会用到防抖,这里先给不知道防抖的同志简单说明一下:
防抖(debounce):n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时。可以简单理解为游戏中的回城,点击回城后等待 n 秒后回城(执行事件),如果在等待过程中再次点击回城则会重新进入回城倒计时。
与此相似的还有一个概念叫做节流,这里也简单说明一下,便于大家区分理解和记忆
节流(throttle):n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效。可以简单理解为游戏中的技能释放,点击后技能即刻释放(执行事件),随即进入冷却无法释放技能,冷却结束后再次点击技能后才会释放。
了解了基础知识之后,我们来看下面这样一段代码,我们要在其上增加防抖
<template>
<div class="container">
<input v-model="text" />
<p class="result">{{ text }}</p>
</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const text = ref('')
</script>
<style scoped>
</style>
经典实现
我们既然要实现防抖,正常情况下上面代码中的v-model就不能直接用了,我们可以先将其改写成原始状态,如下:
<template>
<div class="container">
<input :value="text" @input="handleUpdate" />
<p class="result">{{ text }}</p>
</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const text = ref('')
const handleUpdate = (e) => {
text.value = e.target.value
}
</script>
要实现防抖,即是要让数据的变化延迟执行,这样我们可以设置一个计时器,使其隔段时间再去改变数据
<template>
<div class="container">
<input :value="text" @input="handleUpdate" />
<p class="result">{{ text }}</p>
</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const text = ref('')
let timer
const handleUpdate = (e) => {
clearTimeout(timer)
timer = setTimeout(() => {
text.value = e.target.value
}, 1000)
}
</script>
上面基本实现了防抖,但是每次都要写那么多并不优雅,所以我们将其封装为一个防抖函数放在debounce.js文件中,并在原来的文件中使用它
// debounce.js
export function debounce(func, delay = 1000){
let timer
return function(...args){
clearTimeout(timer)
timer = setTimeout(() => {
func.call(this, ...args)
}, delay)
}
}
<template>
<div class="container">
<input :value="text" @input="handleUpdate" />
<p class="result">{{ text }}</p>
</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { debounce } from './debounce'
const text = ref('')
const update = (e) => (text.value = e.target.value)
const handleUpdate = debounce(update, 1000)
</script>
这样我们就较为优雅地实现了防抖,不过有没有一种方法可以使我们使用最初的v-model的情况下也能实现防抖呢?下面我们进入本文的正题,使用自定义Ref实现防抖
自定义Ref实现防抖
在这之前,我们先来简单了解一下,Vue中的ref()在源码中大概是怎样的形式,直接看代码比文字描述更清晰,我们来简单描述一下ref()的部分大框
function ref(value){
{
get(){
// 依赖收集 track()
return value
},
set(val){
// 派发更新 trigger()
value = val
}
}
}
因为Vue是响应式的,所以需要track()和trigger()进行依赖收集和派发更新形成响应式,有点像发布订阅模式。在了解了以后,我们就可以着手写一个自己的ref()函数了,尤大提前考虑到了这点,为我们提供了customRef方便我们根据需要自定义ref。下面我们就利用customRef来实现防抖,为方便复用我们将代码封装在debounceRef.js中,并在最初的文件中调用
// debounceRef.js
import { customRef } from 'vue'
export function debounceRef(value){
return customRef((track, trigger) => {
return {
get(){
track() // 依赖收集
return value
},
set(val) {
value = val;
trigger(); // 派发更新
}
}
})
}
<template>
<div class="container">
<input v-model="text" />
<p class="result">{{ text }}</p>
</p>
</div>
</template>
<script setup>
import { debounceRef } from './debounceRef'
const text = debounceRef('')
</script>
可以测试一下效果,与使用Vue自身的ref()函数实现的效果是相同的。到此就很简单了,我们只需要如法炮制,使用定时器控制派发更新即可达到防抖的效果,如下:
// debounceRef.js
import { customRef } from 'vue'
export function debounceRef(value, delay = 1000){
let timer
return customRef((track, trigger) => {
return {
get(){
track() // 依赖收集
return value
},
set(val) {
clearTimeout(timer)
timer = setTimeout(() => {
value = val;
trigger(); // 派发更新
}, delay)
}
}
})
}
这样,我们就完美地实现了防抖,在使用中甚至感觉不到有什么变化,一个字——雅!
真是 枯藤老树昏鸦,代码高级优雅~ ヾ(≧▽≦*)o



