Vue3 的 watch,性能真的很差!你們知道怎么優化嗎?
作者:林三心不學挖掘機
如果頁面中使用了大量的 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>
責任編輯:趙寧寧
來源:
前端之神