Vue3 零基础每日笔记(076):防抖节流的正确使用——搜索框、按钮防重复提交、滚动监听

一、前言

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>

两个细节:

  1. 防抖放在"发起请求的函数"上,不是放 input 事件回调------逻辑收敛在一处
  2. 竞态问题:先搜"手机"(慢),又搜"手机壳"(快),可能"手机"的结果后到覆盖新结果------真正的企业方案在请求层做"只认最后一次"(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 中不可再点】

七、踩坑记录

  1. 防抖时间瞎设:300ms 内 PeopleType 很慢、1500ms 又觉得卡------搜索 400~600ms、滚动 150~250ms 是常用区间
  2. 按钮用防抖防连点:狂点后 500ms 仍会发出重复请求------请求型按钮用 saving 锁 + loading
  3. addEventListener 没有 removeEventListener:组件销毁后监听还在,回调报错/内存泄漏------成对出现(useEventListener 可自动清理,079 篇 VueUse 讲)
  4. 防抖函数在 setup 里每次渲染重建 :函数定义放 watch/事件里导致计时器反复重置失效------防抖函数只在 setup 顶层创建一次
  5. 搜索竞态:慢请求后到覆盖新结果------记关键词 AbortController,或后端/接口层串行化
  6. resize 节流忘做:拖动窗口 1 秒触发上百次重算------window resize 一律节流

八、今日小结

  • 口诀:要最终结果用防抖(搜索),要过程反馈用节流(滚动)
  • 按钮防重复 = saving 锁 + :loading,防抖不是干这个的
  • 防抖函数 setup 顶层创建一次 ;监听器成对添加/移除(passive: true 白送的优化)
  • 搜索进阶关键词:竞态 / AbortController

下篇预告

代码层的优化到头了,包越打越大怎么办?下一篇 077 打包优化:visualizer 分析包构成、manualChunks 分包、CDN 外链、gzip 压缩,四大件齐上。

相关推荐
sbjdhjd3 小时前
网工进阶笔记:H3CSE 园区网全栈技术与协议汇总
网络·笔记·云计算·ssh·网络工程·vlan·aaa
她说可以呀4 小时前
Vue插槽
前端·javascript·vue.js
qq_448011164 小时前
Vue + Jinja2两套模版与法冲突造成undefinedError
前端·javascript·vue.js
志尊宝5 小时前
AI原生前端开发(039):AI辅助的Nuxt 4全栈开发
前端·vue.js·ai-native
FRing036 小时前
JavaScript入门到精通三十二(深拷贝、浅拷贝、递归函数)
开发语言·前端·javascript
海绵宝宝的月光宝盒6 小时前
14-AutoCAD-阵列命令应用与案例实战(AR)
经验分享·笔记·自动化·课程设计·制造·学习方法·cad
白山编程大哥7 小时前
接口幂等性是什么?如何设计?
笔记
ACP广源盛139246256737 小时前
6 盘位存储扩展方案选型笔记:YLB3116 @ACP评估记录
笔记·硬件架构·硬件工程·国产芯片
小李不想当小白8 小时前
SPI通信外设与W25Q64 (硬件SPI)(STM32标准库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式
兰亭妙微UI设计公司9 小时前
兰亭妙微ui设计公司分享:B端组件命名如何支持团队协作
前端·javascript·vue.js