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 压缩,四大件齐上。

相关推荐
顽疲1 小时前
从零用 Java 实现小红书 SpringBoot Vue UniApp(23)笔记话题多对多:一条笔记进多个话题池
java·vue.js·spring boot
海绵宝宝转agent1 小时前
2026-10-9 leetcode100刷题+面经整理
笔记·算法·面试
yi0111 小时前
DAY24: LeetCode 167:两数之和 II|从暴力枚举到左右双指针
笔记·python·算法·leetcode·二分查找·双指针
能源革命1 小时前
Vue 网格拖拽布局组件全景指南:7 大方案深度对比
前端·javascript·vue.js
知野小兔1 小时前
JavaScript中Map和Set详解
javascript
yi0112 小时前
LeetCode 15:三数之和|排序 + 双指针,如何避免重复答案?
笔记·python·算法·leetcode·双指针
北风toto2 小时前
数据库笔记:Armstrong 公理系统与集合论的深度辨析
数据库·笔记
EasyBr指纹浏览器2 小时前
我的开源项目:小红书笔记归档,把来源和缺项一起留下
笔记·小红书·开源工具·素材管理·内容归档
daols882 小时前
vue 表格组件 vxe-table 实现自定义多行编辑功能
前端·javascript·vue.js·vxe-table