上一篇提到 导入 deepseek 模型到 trae ,这篇将尝试使用导入的模型生成一个经常遇到的知识点代码样例输出————分析防抖与节流实现差异;
一、理论
| 特性 | 防抖(Debounce) | 节流(Throttle) |
|---|---|---|
| 触发逻辑 | 事件触发后,等待一段时间再执行;若期间再次触发,重新计时。 | 事件触发后,在指定时间内只执行一次,忽略中间的多次触发。 |
| 适用场景 | 适合 “用户停止操作后再执行” 的场景(如搜索框输入、窗口 resize)。 | 适合 “高频触发但需要限制执行频率” 的场景(如按钮点击、滚动事件)。 |
| 接口限制适配 | 无法保证固定间隔,可能在短时间内触发多次(如快速连续点击)。 | 严格控制两次执行的最小间隔,确保不超过接口限制。 |
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
A([开始]):::startend --> B(事件触发):::process
B --> C{选择防抖或节流}:::decision
C -->|防抖| D(设置定时器):::process
C -->|节流| E(检查时间间隔):::process
D --> F{是否在定时器时间内再次触发事件}:::decision
F -->|是| G(清除旧定时器,重新设置定时器):::process
F -->|否| H(执行函数):::process
E --> I{是否达到规定时间间隔}:::decision
I -->|是| J(执行函数,记录执行时间):::process
I -->|否| K(忽略本次触发):::process
H --> L([结束]):::startend
J --> L
K --> B
G --> F
时序图
sequenceDiagram
participant User
participant Debounce
participant Function
User->>Debounce: 触发事件
Debounce->>Function: 设置定时器
User->>Debounce: 再次触发事件
Debounce->>Function: 清除旧定时器,重新设置
User->>Debounce: 再次触发事件
Debounce->>Function: 清除旧定时器,重新设置
Note over Debounce,Function: 一段时间无触发
Debounce->>Function: 执行函数
- 用户触发事件:用户多次触发事件。
- 防抖处理:每次触发事件时,防抖机制会清除之前设置的定时器,并重新设置一个新的定时器。
- 执行函数:当在一段时间内没有新的事件触发时,定时器到期,执行目标函数。
sequenceDiagram
participant User
participant Throttle
participant Function
User->>Throttle: 触发事件
Throttle->>Function: 检查时间间隔
Throttle->>Function: 达到间隔,执行函数
User->>Throttle: 再次触发事件
Throttle->>Function: 未达间隔,忽略
User->>Throttle: 再次触发事件
Throttle->>Function: 未达间隔,忽略
Note over Throttle,Function: 达到时间间隔
Throttle->>Function: 达到间隔,执行函数
-
用户触发事件:用户多次触发事件。
-
节流处理:每次触发事件时,节流机制会检查是否达到规定的时间间隔。
-
执行函数:如果达到时间间隔,则执行目标函数;如果未达到时间间隔,则忽略本次触发。
二、coding
使用 trae 生成代码
此处应该选择 Builder 模式,更适用于输出 demo
明显看到防抖是最后手势停止时候才有值变化,节流是间断周期增加值
三、场景
在某业务场景中,有一个用于控制下位机的按钮交互场景,下位机对于处理指令有一定间隔要求(假设 300ms/次),需要满足下位机稳定执行指令并且避免用户体验卡顿,此处结合节流策略实现
为什么选择节流?
- 接口硬性时间限制 你的接口要求 两次请求必须间隔至少 300ms,节流可以通过固定时间窗口(如 300ms)强制限制请求频率,确保不会在间隔内发起多次请求。而防抖依赖“用户停止触发”,如果用户在 300ms 内持续操作(如快速点击),仍可能触发多次请求,导致接口过载。
- 界面交互绑定 若功能与界面操作(如按钮点击、滑动、拖拽)强相关,节流能更好地匹配用户体验: - 例如按钮点击场景,节流期间可禁用按钮或显示加载状态,避免用户误操作; - 防抖可能导致用户点击后无即时反馈(需等待超时),影响交互体验。
1. 基础节流函数
DateTime? _lastCallTime;
void call(Function callback) {
final now = DateTime.now();
if (_lastCallTime == null || now.difference(_lastCallTime!) >= interval) {
_lastCallTime = now;
callback(); // 执行触发逻辑
}
}
}
2. 结合界面状态管理(如按钮点击)
class MyButton extends StatefulWidget {
final VoidCallback onTap;
const MyButton({Key? key, required this.onTap}) : super(key: key);
@override
State<MyButton> createState() => _MyButtonState();
}
class _MyButtonState extends State<MyButton> {
final Throttle _throttle = Throttle(Duration(milliseconds: 200));
bool _isLoading = false; // 控制按钮状态
void _handleTap() {
if (_isLoading) return; // 防止重复触发
_throttle.call(() async {
setState(() {
_isLoading = true; // 禁用按钮
});
try {
await widget.onTap(); // 执行接口请求
} finally {
setState(() {
_isLoading = false; // 恢复按钮状态
});
}
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _isLoading ? null : _handleTap,
child: Text(_isLoading ? '加载中...' : '点击'),
);
}
}
选择节流因为它能严格控制接口调用频率,满足 300ms 间隔要求,同时适配界面交互场景。通过将节流逻辑与界面状态(如按钮禁用、加载状态)绑定,既能保证接口稳定性,又能提升用户体验。
如果后续有更复杂的频率控制需求(如动态调整间隔),可考虑使用成熟的状态管理库(如 Bloc、Provider)或第三方包(如 throttling)来优化实现。
注意事项
- 时间窗口设置 节流间隔需严格匹配接口限制,避免设置过小导致接口报错,或过大影响用户体验;
- 界面反馈 - 在节流期间通过禁用按钮、加载动画等方式明确告知用户操作状态,避免重复点击;若接口请求耗时较长(超过 300ms),需额外处理并发请求(如排队或取消旧请求);
- 防抖的适用例外 若场景是“用户输入结束后触发请求”(如搜索框输入),且输入频率可能高于 300ms,则可结合防抖(如等待 300ms 无输入再请求),但需确保最后一次请求与上一次请求间隔 ≥ 300ms(可通过节流+防抖组合实现);
demo 地址 flutter_ioc/debounce_throttle at master · lizy-coding/flutter_ioc