直接回答:并发渲染不是多线程渲染,而是“可中断的渲染 + 优先级调度”:低优先级更新在后台准备新 UI,期间高优先级更新(用户输入)可插队,准备完成前屏幕保持旧 UI 且旧 UI 仍然可交互。useTransition 把一次更新标记为过渡(低优),解决“输入卡顿”——输入框回显高优立即渲染,下方大列表过滤低优后台算。

展开解析:时间切片的工作方式:workLoop 每执行一个 Fiber 节点检查 shouldYield(默认 5ms 时间片),到期让出主线程用 MessageChannel 排回队列(不用 setTimeout 因为 4ms 钳制),浏览器趁机处理绘制与输入。插队机制:渲染进行到一半来了高优更新,React 丢弃或复用已完成的 workInProgress 子树,从高优车道重新出发——这是“浪费”但正确,因为一致性优先。useTransition 的状态机:isPending 期间旧 UI 保留且可交互(比如输入框继续接受输入),新 UI 就绪后一次性切换;对比 setState 直接更新——长列表渲染阻塞输入,每个按键都卡。与防抖的本质区别:防抖延迟计算减少次数,transition 让高优任务随时打断,且保留中间响应性。Suspense 联动:过渡中数据未就绪时保持旧内容而非闪 loading,这才有“拉取时渲染”的顺滑。常见误用:把 cheap 更新也包 transition(无收益)、在 transition 里做受控输入(输入必须同步车道)、期待 transition 减少计算量(它只是调度不省算力)。

追问方向:为什么用 MessageChannel 而非 requestIdleCallback?useDeferredValue 与 useTransition 的差别?

(约 490 字)