踩坑指南:VueUse useThrottleFn 为什么"不听话"?

169 阅读4分钟

踩坑指南:VueUse useThrottleFn 为什么"不听话"?

当你满心欢喜地用 useThrottleFn 来节流函数,却发现它还在"偷偷"执行额外的调用时,恭喜你踩到了一个经典的坑!让我们来看看这个"调皮"的 trailing 参数是如何在 8.7.3 版本中"搞事情"的。🤡

🎯 问题现象:我的节流怎么"失效"了?

你是否遇到过这样的情况:

// 你以为这样写,函数只会在开始时执行一次
const save = useThrottleFn(saveData, 1000);

// 疯狂点击保存按钮
save(); // 执行 ✅
save(); // 不执行 ✅
save(); // 不执行 ✅
save(); // 不执行 ✅
// 1秒后...
// WTF?怎么又执行了一次?😱

如果你正在使用 VueUse 8.7.3 版本,那么恭喜你中奖了!这不是 bug,这是"特性"!🎊

🔍 真相大白:trailing 参数在作怪

useThrottleFn 是 VueUse 提供的节流函数工具,但它有个"贴心"的默认行为:在节流周期结束后,还会"善意"地帮你执行最后一次调用

这就是为什么你的函数会在你以为已经结束的时候,突然又执行一次的原因!😅😅😅

基本语法

v13.8.0 版本 官网 Type Declarations

/**
 * Throttle execution of a function. Especially useful for rate limiting
 * execution of handlers on events like resize and scroll.
 *
 * @param   fn             A function to be executed after delay milliseconds. The `this` context and all arguments are passed through, as-is,
 *                                    to `callback` when the throttled-function is executed.
 * @param   ms             A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.
 *                                    (default value: 200)
 *
 * @param [trailing] if true, call fn again after the time is up (default value: false)
 *
 * @param [leading] if true, call fn on the leading edge of the ms timeout (default value: true)
 *
 * @param [rejectOnCancel] if true, reject the last call if it's been cancel (default value: false)
 *
 * @return  A new, throttled, function.
 *
 * @__NO_SIDE_EFFECTS__
 */
export declare function useThrottleFn<T extends FunctionArgs>(
  fn: T,
  ms?: MaybeRefOrGetter<number>,
  trailing?: boolean,
  leading?: boolean,
  rejectOnCancel?: boolean
): PromisifyFn<T>;
import { useThrottleFn } from "@vueuse/core";

const throttledFn = useThrottleFn(fn, ms, trailing, leading, rejectOnCancel);

🔧 参数详解

1. fn (必需)

  • 类型: (...args: any[]) => any
  • 描述: 需要进行节流处理的目标函数

2. ms (必需)

  • 类型: number
  • 描述: 节流间隔时间,单位毫秒
  • 默认值: 无(必须指定)

3. trailing ⚠️ 罪魁祸首就是你!

  • 类型: boolean
  • 描述: 是否在节流周期结束后执行最后一次调用
  • 8.7.3 版本默认值: true 🤡(这就是你踩坑的原因!)
  • 后续版本变更:
    • v8.9.0 之前: true (包括你用的 8.7.3)
    • v8.9.0 及之后: false 🎉(终于!!!)
🎭 8.7.3 版本的"惊喜"行为
// 8.7.3 版本:trailing 默认为 true
const throttledFn = useThrottleFn(saveData, 1000);
// 等价于:useThrottleFn(saveData, 1000, true, true)

// 你期望的行为 vs 实际行为:
点击(); // 立即执行 ✅ 期望 ✅ 实际
点击(); // 被忽略   ✅ 期望 ✅ 实际
点击(); // 被忽略   ✅ 期望 ✅ 实际
点击(); // 被忽略   ✅ 期望 ✅ 实际
// 1秒后...
// 什么都不发生 ✅ 期望
// 又执行了一次 😱 实际!trailing 的"杰作"
💡 解决方案:显式关闭 trailing
// 8.7.3 版本的正确用法:手动关闭 trailing
const throttledFn = useThrottleFn(saveData, 1000, false);
// 这样就只会在开始时执行,结束时不会再"加餐"

// 或者完整写法更清晰
const throttledFn = useThrottleFn(
  saveData,
  1000, // 间隔时间
  false, // trailing: 关闭结束时执行
  true // leading: 保持开始时执行
);

4. leading

  • 类型: boolean
  • 描述: 是否在节流周期开始时立即执行
  • 默认值: true

5. rejectOnCancel

  • 类型: boolean
  • 描述: 当节流函数被取消时,是否拒绝 Promise
  • 默认值: false
// 返回 Promise 的异步函数
async function asyncFunction() {
  return "result";
}

const throttledAsync = useThrottleFn(asyncFunction, 1000, false, true, true);

// 使用示例
try {
  const result = await throttledAsync();
  console.log(result);
} catch (error) {
  console.log("被取消:", error);
}

🎉 踩坑总结:如何避免被 useThrottleFn "背刺"

如果你正在使用 VueUse 8.7.3,记住这几个要点:

🤡 问题根源trailing 参数默认为 true,会在节流周期结束后"偷偷"执行最后一次调用
🔧 快速修复:显式设置 trailing: false,告诉函数"适可而止"
📈 版本升级:如果可能,升级到 v8.9.0+ 享受更合理的默认行为
⚠️ 注意兼容:升级版本时要检查现有代码,避免行为反转

🏆 最佳实践(8.7.3 版本)

// ❌ 错误:会有"惊喜"的额外执行
const badThrottle = useThrottleFn(saveData, 1000);

// ✅ 正确:明确控制行为
const goodThrottle = useThrottleFn(saveData, 1000, false, true);

// ✅ 更清晰:参数含义一目了然
const clearThrottle = useThrottleFn(
  saveData,
  1000, // 间隔时间
  false, // trailing: 不要在结束时"加餐"
  true // leading: 开始时立即执行
);

📚 记忆口诀

八点七点三版本用,trailing 默认 true 坑
显式 false 来救场,节流才能按预期 🎵


记住:在 8.7.3 版本中,不指定 trailing 参数 = 给自己埋坑! 💣

想了解更多 VueUse 踩坑指南?关注我避免更多"惊喜"! 🕳️