文章目录
- 先给结论
- 先给一张总对照表
- 一、setTimeout:只是"往后丢一会儿"
- 二、debounce:减少执行次数,不是调度
- [三、startTransition:React 级别的"让路"](#三、startTransition:React 级别的“让路”)
-
- 代码
- 它到底做了什么?
- 时间线对比(非常关键)
-
- [setTimeout / debounce](#setTimeout / debounce)
- startTransition
- 四、三者最容易搞混的一点
- 五、正确的组合方式(生产级)
-
- [debounce + startTransition(最强)](#debounce + startTransition(最强))
- 错误组合(常见)
- 六、一个表格搜索的真实决策树
- [七、为什么 React 团队不推荐 setTimeout?](#七、为什么 React 团队不推荐 setTimeout?)
- 八、用一句工程化标准帮你记住
- 九、最后一段"刻进脑子"的话
先给结论
setTimeout / debounce = 时间层面的延迟
startTransition = UI 优先级 & 可中断调度
它们解决的是 完全不同的问题。
先给一张总对照表
| 维度 | setTimeout | debounce | startTransition |
|---|---|---|---|
| 本质 | JS 定时器 | JS 定时策略 | React 调度语义 |
| 是否理解 UI | ❌ | ❌ | ✅ |
| 是否可中断 | ❌ | ❌ | ✅ |
| 是否降低计算次数 | ❌ | ✅ | ❌ |
| 是否防抖 | ❌ | ✅ | ❌ |
| 是否解决卡输入 | ❌ | ⚠️ 部分 | ✅ |
| React 推荐 | ❌ | ⚠️ | ✅ |
一、setTimeout:只是"往后丢一会儿"
代码
ts
setTimeout(() => {
setList(filter(data))
}, 0)
实际发生了什么?
- 把任务放进 macrotask queue
- 当前 call stack 执行完再跑
- 但一旦开始执行,就不能被打断
为什么不能解决卡顿?
输入
↓
JS 空闲
↓
setTimeout callback 开始
↓
filter(data) 占满 500ms
↓
输入仍然卡
它只是换了个时间点卡你
什么时候还能用?
- 非 UI 逻辑
- 低优先级副作用
- 日志 / 打点
二、debounce:减少执行次数,不是调度
代码
ts
const debouncedFilter = debounce((v) => {
setList(filter(data, v))
}, 300)
它解决的是什么?
"你别每次都算"
- 用户输入 10 次
- debounce 后只算 1 次
它没解决什么?
- 一旦真的执行
filter(data)仍然是同步的- 仍然会卡主线程
debounce = 减少次数,不是让 UI 优先
debounce 的适用场景
| 场景 | 是否合适 |
|---|---|
| 请求接口 | ✅ |
| 自动保存 | ✅ |
| 搜索接口 | ✅ |
| 本地大计算 | ❌ |
三、startTransition:React 级别的"让路"
代码
ts
startTransition(() => {
setList(filter(data))
})
它到底做了什么?
React 在内部标记:
"这次更新可以被打断"
然后:
- 输入事件 → 高优先级
- transition 更新 → 低优先级
- React 在空闲时间切片执行
时间线对比(非常关键)
setTimeout / debounce
filter 开始
↓
500ms 主线程占满
↓
输入卡
startTransition
filter 执行一部分
↓
用户输入
↓
React 中断 filter
↓
更新 input
↓
继续 filter
这是本质差异。
四、三者最容易搞混的一点
startTransition 不会减少计算量
ts
filter(data) // 还是会算
它只是:
- 不一次性算完
- 不阻塞 urgent 更新
五、正确的组合方式(生产级)
debounce + startTransition(最强)
ts
const onChange = debounce((value) => {
startTransition(() => {
setList(filter(data, value))
})
}, 200)
既少算,又不卡
错误组合(常见)
ts
startTransition(() => {
debounce(() => setList(...), 300)()
})
完全没意义。
六、一个表格搜索的真实决策树
输入 → 表格过滤 → 10000 行
| 问题 | 答案 |
|---|---|
| 每次输入都要算吗? | ❌ |
| 能不能晚点算? | ✅ |
| 用户在 input 框输入必须立即显示文字吗? | ✅ |
debounce + startTransition
七、为什么 React 团队不推荐 setTimeout?
因为:
- 它绕过 React Scheduler
- React 无法协调更新
- 在 Concurrent 模式下是"黑盒"
八、用一句工程化标准帮你记住
JS 时间工具(setTimeout / debounce)
管"什么时候执行"
React 工具(startTransition)
管"谁先执行"
九、最后一段"刻进脑子"的话
setTimeout:
"等会再卡你"
debounce:
"少卡几次"
startTransition:
"先让用户动起来"