vue3 的 watch,性能真的很差!你们知道怎么优化吗?
最近发现公司某个 Vue 页面非常卡,一看原来是因为页面中使用了大量的 watch ,但是又没有进行防抖或者节流的限制,导致了触发非常频繁,所以页面非常卡顿。
所以想着,想分享一下如何给 watch 加上 防抖、节流。
一、前置知识:防抖与节流
在实现监听器之前,我们需要理解两个核心概念:
- 防抖(Debounce): 在连续触发时,只在最后一次操作后等待指定时间执行
- 节流(Throttle): 在连续触发时,保证固定时间间隔内只执行一次
二、基础监听器实现
我们先实现一个简单的响应式监听器,基于Vue的watch函数:
/**
* 基础监听器
* @param {Ref} source 要监听的响应式数据
* @param {Function} callback 变化回调
* @param {Object} options 监听选项
*/
function basicWatcher(source, callback, options = {}) {
let cleanup = () => {}
const stop = watch(source, (value, oldValue, onCleanup) => {
// 清除之前的副作用
cleanup()
// 注册新的清理函数
onCleanup(() => {
cleanup = () => {}
})
// 执行回调
callback(value, oldValue)
}, options)
return stop
}
三、实现防抖监听器(watchDebounced)
实现要点:
- 使用 setTimeout 延迟回调执行
- 每次新变化时重置定时器
- 清理函数确保组件卸载时终止等待中的回调
/**
* 防抖监听器
* @param {Ref} source 要监听的响应式数据
* @param {Function} callback 回调函数
* @param {number} delay 防抖延迟时间(毫秒)
* @param {Object} options 监听选项
*/
function watchDebounced(source, callback, delay = 300, options = {}) {
let timeoutId = null
let cleanup = () => {}
const stop = watch(source, (value, oldValue, onCleanup) => {
// 清除之前的定时器和副作用
clearTimeout(timeoutId)
cleanup()
// 设置新的定时器
timeoutId = setTimeout(() => {
// 执行回调时绑定正确的this上下文
callback.call(this, value, oldValue)
}, delay)
// 注册清理函数
onCleanup(() => {
clearTimeout(timeoutId)
cleanup = () => {}
})
}, options)
return stop
}
四、实现节流监听器(watchThrottled)
实现要点:
- 使用时间戳计算剩余可执行时间
- 未到间隔时间时,设置剩余时间的定时器
- 保证间隔时间内至少执行一次
/**
* 节流监听器
* @param {Ref} source 要监听的响应式数据
* @param {Function} callback 回调函数
* @param {number} interval 节流间隔(毫秒)
* @param {Object} options 监听选项
*/
function watchThrottled(source, callback, interval = 300, options = {}) {
let lastExecTime = 0
let cleanup = () => {}
let timeoutId = null
const stop = watch(source, (value, oldValue, onCleanup) => {
const now = Date.now()
const elapsed = now - lastExecTime
// 清除等待中的定时器
clearTimeout(timeoutId)
cleanup()
if (elapsed >= interval) {
// 立即执行
callback(value, oldValue)
lastExecTime = now
} else {
// 设置剩余时间的定时器
timeoutId = setTimeout(() => {
callback(value, oldValue)
lastExecTime = Date.now()
}, interval - elapsed)
}
onCleanup(() => {
clearTimeout(timeoutId)
cleanup = () => {}
})
}, options)
return stop
}
五、使用示例
<script setup>
import { ref } from 'vue'
const searchKeyword = ref('')
// 防抖监听示例
watchDebounced(
searchKeyword,
(newVal) => {
console.log('防抖搜索:', newVal)
// 这里可以执行API请求
},
500
)
// 节流监听示例
watchThrottled(
searchKeyword,
(newVal) => {
console.log('节流记录:', newVal)
// 这里可以执行高频状态记录
},
1000
)
</script>