一、前言
038 篇封装了 useDebounceFn / useThrottleFn,本篇讲"用在哪、怎么用对":搜索框防抖、按钮防重复提交、滚动监听节流三大场景,以及一组高频误区(按钮该用防抖还是 loading?卸载要不要清理?)。
二、先分清:防抖 vs 节流
| 防抖 debounce | 节流 throttle | |
|---|---|---|
| 行为 | 停手一段时间才执行(一直动就一直不执行) | 固定频率执行(再密集也按间隔放行) |
| 类比 | 电梯关门:有人进就一直等,没人进才关 | 红绿灯:固定周期放行一批 |
| 场景 | 搜索输入、resize 结束后计算 | 滚动监听、鼠标移动、按钮限流 |
选择口诀:要"最终结果"用防抖,要"过程反馈"用节流。
三、场景一:搜索框防抖(最高频)
输入"手机壳"会触发 3 次 input------理想行为:停止输入 500ms 后只搜一次。
vue
<template>
<el-input v-model="keyword" placeholder="搜索商品" clearable />
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useDebounceFn } from '@vueuse/core' // 或 038 篇自封装版
const keyword = ref('')
// ★ 防抖后的搜索函数
const debouncedSearch = useDebounceFn(async (kw: string) => {
if (!kw.trim()) return
const res = await getGoodsList({ keyword: kw, page: 1 })
list.value = res.data.list
}, 500)
// watch 输入变化 → 交给防抖函数
watch(keyword, (kw) => debouncedSearch(kw))
</script>
两个细节:
- 防抖放在"发起请求的函数"上,不是放 input 事件回调------逻辑收敛在一处
- 竞态问题:先搜"手机"(慢),又搜"手机壳"(快),可能"手机"的结果后到覆盖新结果------真正的企业方案在请求层做"只认最后一次"(AbortController),先记关键词,062 篇的 request 封装可扩展
按钮搜索(点一下搜一下)则不需要防抖,直接调接口 + 按钮 loading。
四、场景二:按钮防重复提交
ts
const saving = ref(false)
async function onSave() {
if (saving.value) return // ★ 第一道锁
saving.value = true
try {
await createOrder()
ElMessage.success('提交成功')
} finally {
saving.value = false // ★ 无论成败都解锁
}
}
vue
<el-button type="primary" :loading="saving" @click="onSave">提交订单</el-button>
为什么不用防抖锁按钮?防抖是"延迟执行",用户狂点后 500ms 还是会发;而业务要求的是"请求期间彻底禁止"------语义不同。结论:
text
防重复提交 = saving 锁 + :loading(067 篇同款)
防抖 = 处理"连续输入/连续触发"的收敛
(若按钮是"点赞"这类幂等高频操作,可用节流给个 1s 限制,属于产品策略。)
五、场景三:滚动 / resize 节流
ts
import { useThrottleFn } from '@vueuse/core'
import { onBeforeUnmount } from 'vue'
// ★ 节流:最高每 200ms 响应一次滚动
const onScroll = useThrottleFn(() => {
// 计算"回到顶部"按钮显隐、吸顶判断等
showTop.value = window.scrollY > 300
}, 200)
window.addEventListener('scroll', onScroll, { passive: true })
// ★ 有 add 就必须有 remove(037 篇铁律)
onBeforeUnmount(() => {
window.removeEventListener('scroll', onScroll)
})
{ passive: true } 告诉浏览器"我不调 preventDefault",滚动主线程不被阻塞,白送的优化。
六、完整可运行代码:三种场景一页看全
vue
<!-- src/views/DebounceView.vue -->
<template>
<h4>① 搜索防抖</h4>
<el-input v-model="keyword" placeholder="停止输入 500ms 后提示" />
<p>已触发搜索 {{ searchCount }} 次</p>
<h4>② 按钮锁</h4>
<el-button type="primary" :loading="saving" @click="onSave">提交(2s 模拟)</el-button>
<h4>③ 滚动节流</h4>
<div class="tall">滚动页面,看右下角按钮显隐(200ms 节流)</div>
<el-button v-show="showTop" class="top" type="primary" @click="toTop">TOP</el-button>
</template>
<script setup lang="ts">
import { ref, watch, onBeforeUnmount } from 'vue'
import { useDebounceFn, useThrottleFn } from '@vueuse/core'
// ① 防抖
const keyword = ref('')
const searchCount = ref(0)
const debouncedSearch = useDebounceFn(() => searchCount.value++, 500)
watch(keyword, () => debouncedSearch())
// ② 锁
const saving = ref(false)
async function onSave() {
if (saving.value) return
saving.value = true
await new Promise((r) => setTimeout(r, 2000))
saving.value = false
ElMessage.success('提交成功')
}
// ③ 节流
const showTop = ref(false)
const onScroll = useThrottleFn(() => (showTop.value = window.scrollY > 300), 200)
window.addEventListener('scroll', onScroll, { passive: true })
onBeforeUnmount(() => window.removeEventListener('scroll', onScroll))
function toTop() { window.scrollTo({ top: 0, behavior: 'smooth' }) }
</script>
<style scoped>
.tall { height: 1500px; }
.top { position: fixed; right: 24px; bottom: 24px; }
</style>
【截图位置:连续输入"手机壳"只触发 1 次搜索;提交按钮 loading 中不可再点】
七、踩坑记录
- 防抖时间瞎设:300ms 内 PeopleType 很慢、1500ms 又觉得卡------搜索 400~600ms、滚动 150~250ms 是常用区间
- 按钮用防抖防连点:狂点后 500ms 仍会发出重复请求------请求型按钮用 saving 锁 + loading
- addEventListener 没有 removeEventListener:组件销毁后监听还在,回调报错/内存泄漏------成对出现(useEventListener 可自动清理,079 篇 VueUse 讲)
- 防抖函数在 setup 里每次渲染重建 :函数定义放 watch/事件里导致计时器反复重置失效------防抖函数只在 setup 顶层创建一次
- 搜索竞态:慢请求后到覆盖新结果------记关键词 AbortController,或后端/接口层串行化
- resize 节流忘做:拖动窗口 1 秒触发上百次重算------window resize 一律节流
八、今日小结
- 口诀:要最终结果用防抖(搜索),要过程反馈用节流(滚动)
- 按钮防重复 = saving 锁 + :loading,防抖不是干这个的
- 防抖函数 setup 顶层创建一次 ;监听器成对添加/移除(passive: true 白送的优化)
- 搜索进阶关键词:竞态 / AbortController
下篇预告
代码层的优化到头了,包越打越大怎么办?下一篇 077 打包优化:visualizer 分析包构成、manualChunks 分包、CDN 外链、gzip 压缩,四大件齐上。