- 前端
- 教程
【免费下载链接】preguntas-entrevista-react
Preguntas típicas sobre React para entrevistas de trabajo ⚛️
本指南以仓库内 Vercel 工程团队维护的性能规则文档 rerender-transitions.md 为骨架,系统讲解如何用startTransition将高频、非紧急的 React 状态更新标记为"过渡更新",避免渲染阻塞拖慢用户交互。读完你将掌握紧急更新与非紧急更新的优先级模型、startTransition与useTransition的正确用法,以及它在滚动跟踪、搜索过滤等真实场景下的落地写法,并顺带了解它与useDeferredValue、useRef等同类优化手段的分工。
问题场景:高频非紧急更新为什么会卡住 UI
在 React 中,每一次setState都可能触发一次组件树重渲染。当更新频率极高、渲染代价又不低时,两者叠加就会让用户明显感到"掉帧""卡顿"。最常见的受害者之一就是滚动跟踪:
function ScrollTracker() { const [scrollY, setScrollY] = useState(0) useEffect(() => { const handler = () => setScrollY(window.scrollY) window.addEventListener('scroll', handler, { passive: true }) return () => window.removeEventListener('scroll', handler) }, []) }这段代码的问题在于:scroll事件在用户滚动过程中以极高频率触发,每一次触发都会调用setScrollY并引发一次同步渲染。如果这个组件或其子树的重渲染成本较高(例如下游还挂着复杂的列表、图表),渲染就会抢占主线程,用户会看到滚动卡顿、输入迟滞——因为浏览器在等待 React 完成渲染后才继续处理下一次交互。
从规则文件的前置元数据可以看出它的定位:impact: MEDIUM,impactDescription: maintains UI responsiveness,标签为rerender, transitions, startTransition, performance。它属于 SKILL.md 中第 5 类"Re-render Optimization(重渲染优化)"规则,属于中等影响级别,目标是维持 UI 响应性。该规则原文明确给出了判断标准:"Mark frequent, non-urgent state updates as transitions to maintain UI responsiveness"——把频繁且非紧急的状态更新标记为 transition。
解决方案:用 startTransition 包裹非紧急更新
修复方式非常直接:把非紧急的更新放进startTransition回调里,告诉 React"这次更新不着急,可以晚点再算":
import { startTransition } from 'react' function ScrollTracker() { const [scrollY, setScrollY] = useState(0) useEffect(() => { const handler = () => { startTransition(() => setScrollY(window.scrollY)) } window.addEventListener('scroll', handler, { passive: true }) return () => window.removeEventListener('scroll', handler) }, []) }对比两个版本,差异只有一行:setScrollY(window.scrollY)被包进了startTransition(() => ...)。效果上,滚动位置的更新变成了"可延迟、可中断"的过渡更新:React 会优先处理用户输入、点击等紧急交互,在空闲时再完成滚动位置的渲染;即使滚动事件连续爆发,React 也可以丢弃中间结果,只渲染最新的位置。
细心的读者还会注意到:正确版本中滚动监听器额外传了{ passive: true }。这对应同技能库中的另一条规则 client-passive-event-listeners.md——浏览器默认会等待监听器执行完毕、确认没有调用preventDefault()后才继续滚动,从而产生滚动延迟;标记passive: true后浏览器即可立即滚动。passive解决的是"事件本身阻塞滚动"的问题,startTransition解决的是"状态更新渲染阻塞主线程"的问题,二者互为补充。
原理:紧急更新与非紧急更新的优先级模型
要理解startTransition为什么有效,需要先了解 React 对更新的两种分类。仓库中的问答内容 que-es-start-transition-y-en-que-se-diferencia-de-actualizar-el-estado-de-forma-normal.json 对此有清晰归纳:
- 紧急更新(Urgent Updates):直接由用户交互触发,如输入框打字、点击按钮。这类更新必须立刻反映在界面上,否则用户会感到延迟。
- 非紧急更新(Transition Updates):由
startTransition标记的更新。React 可以中断或推迟它们,把优先级让给紧急更新。
典型的组合用法是把两者放在同一个事件处理器里:紧急的部分立刻执行,非紧急的部分交给 transition:
import { useState, startTransition } from 'react' function Search({ items }: { items: string[] }) { const [query, setQuery] = useState('') const [filtered, setFiltered] = useState(items) function onChange(e: React.ChangeEvent<HTMLInputElement>) { const value = e.target.value setQuery(value) // 紧急:输入框必须即时回显 startTransition(() => { setFiltered(items.filter(i => i.includes(value))) // 非紧急:可延迟 }) } return ( <> <input value={query} onChange={onChange} /> <List items={filtered} /> </> ) }其中setQuery(value)是紧急更新,让输入框即时响应;setFiltered(...)是过渡更新,涉及整个列表的过滤与重渲染,可以推迟到空闲时段完成。这正是该规则在滚动场景之外的另一种典型应用:搜索过滤。
同一个文档还指出了两个容易混淆的边界:
- 过渡更新可能"落后"于紧急更新,因此界面可能出现短暂的新旧状态混叠——这是设计使然,用于换取流畅度;
startTransition不能替代useMemo/useCallback。它解决的不是"避免重复计算",而是"渲染优先级"问题——前者管缓存,后者管调度。
useTransition 钩子:用 isPending 感知过渡进度
如果只是把更新标记为"非紧急",使用顶层导出的startTransition即可。但当你需要在界面上反馈"正在更新中"时,应该改用useTransition钩子,它额外返回isPending标志。仓库问答 para-que-sirve-el-hook-use-transition-y-cuando-deberias-usarlo.json 给出了完整示例:
import { useState, useTransition } from 'react' function FilterableList({ items }: { items: string[] }) { const [query, setQuery] = useState('') const [results, setResults] = useState(items) const [isPending, startTransition] = useTransition() const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { const value = event.target.value setQuery(value) startTransition(() => { const filtered = items.filter(item => item.toLowerCase().includes(value.toLowerCase()) ) setResults(filtered) }) } return ( <> <input value={query} onChange={handleChange} /> {isPending && <p>Cargando resultados...</p>} <ul> {results.map(item => ( <li key={item}>{item}</li> ))} </ul> </> ) }useTransition()返回[isPending, startTransition]:
startTransition与顶层导出的同名函数行为一致,用于标记非紧急更新;isPending为true表示当前有过渡更新尚未完成,可以在界面上显示加载提示(如示例中的"Cargando resultados..."),避免用户以为界面卡死。
同技能库中的 rendering-usetransition-loading.md 还进一步建议:用useTransition替代手写的isLoading状态。手动管理加载态需要自己维护setIsLoading(true/false)的配对,容易出错;而useTransition自带以下好处:
- 自动的 pending 状态:无需手动开关加载标志;
- 错误韧性:即使过渡抛错,
isPending也能正确复位; - 打断处理:新的过渡会自动取消/取代尚未完成的旧过渡,天然避免竞态;
- 该规则示例中还展示了 React 19 中
startTransition可接受 async 函数、在过渡内进行异步取数的写法。
使用边界:什么时候该用,什么时候不该用
startTransition不是万能钥匙,使用前先回答两个问题:更新是否高频?是否非紧急?两个条件都满足才适合用。
适合使用的场景:
- 滚动位置、鼠标轨迹等高频跟踪数据的渲染(规则主场景);
- 搜索/过滤大型列表,输入即时回显、列表结果可稍后渲染;
- 图表、可视化等代价高昂的派生渲染响应输入;
- 数据量大的列表切换、排序等不要求即时反馈的操作。
不适合使用的场景:
- 必须即时反馈的紧急更新(如输入框本身的值、表单校验错误提示),强行包进 transition 反而会引入可见延迟;
- 低频、一次性的更新——没有阻塞压力,包一层 transition 徒增复杂度;
- 依赖渲染顺序的代码——过渡更新可能被中断或滞后,不应假设"包裹后立刻生效"。
另外注意:规则原文的滚动示例把每次滚动更新都包进 transition,这是一种"兜底"写法。如果某个高频值根本不需要驱动 UI 变化,同技能库的姊妹规则 rerender-use-ref-transient-values.md 给出了更激进的优化:把这类瞬时值放进useRef,完全不触发渲染(ref 更新不会导致 re-render),仅在需要时直接操作 DOM。两条规则的取舍是——需要渲染时用 transition 降低优先级,不需要渲染时用 ref 彻底跳过渲染。例如同样是鼠标跟踪:如果要点位是渲染出来的元素,用useTransition保流畅;如果只需移动一个绝对定位的小点,useRef+ 直接改transform更划算。
与 useDeferredValue 的分工
过渡相关的优化还有一个近亲:useDeferredValue。规则 rerender-use-deferred-value.md 与本文规则同属重渲染优化类,处理的是"输入触发昂贵派生渲染"的场景:
function Search({ items }: { items: Item[] }) { const [query, setQuery] = useState('') const deferredQuery = useDeferredValue(query) const filtered = useMemo( () => items.filter(item => fuzzyMatch(item, deferredQuery)), [items, deferredQuery] ) const isStale = query !== deferredQuery return ( <> <input value={query} onChange={e => setQuery(e.target.value)} /> <div style={{ opacity: isStale ? 0.7 : 1 }}> <ResultsList results={filtered} /> </div> </> ) }二者的区别在于控制粒度:
startTransition/useTransition把更新的执行标记为非紧急,你显式决定哪段setState可以延迟;useDeferredValue把某个值标记为可滞后,下游基于deferredQuery的渲染自动降级为低优先级,且必须搭配useMemo把昂贵计算缓存起来,否则延迟没有任何意义(该规则在文末有明确 Note)。
从仓库内容看,que-es-start-transition-y-en-que-se-diferencia-de-actualizar-el-estado-de-forma-normal.json 与 para-que-sirve-el-hook-use-transition-y-cuando-deberias-usarlo.json 这两份问答内容也共同佐证了同一结论:startTransition不解决重复计算问题(那是useMemo/useCallback的职责),它解决的是渲染调度优先级问题。实践时可按需组合——过滤逻辑用useTransition包裹,昂贵的模糊匹配计算用useMemo缓存。
规则在项目中的定位与落地建议
本规则文件是 .agents/skills/vercel-react-best-practices 技能包的一部分,该技能包由 Vercel 工程团队维护(metadata.json 标注了organization: Vercel Engineering),面向 React/Next.js 性能优化,横跨 8 个类别,按影响等级从 CRITICAL 到 LOW 排序,供 Agent 与 LLM 在编写、审查、重构 React 代码时引用。每条规则文件都遵循统一结构:前置元数据(标题、影响等级、影响描述、标签)+ 问题说明 + 错误示例 + 正确示例 + 参考说明(详见 README.md)。
rerender-transitions.md位于第 5 类"Re-render Optimization",影响等级 MEDIUM,与之同类的规则还有rerender-use-deferred-value、rerender-use-ref-transient-values、rerender-memo等。落到实际项目时,可按以下顺序自查滚动/输入类高频场景:
- 这个值是否必须驱动 UI?不需要 → 用
useRef存储(见 rerender-use-ref-transient-values.md); - 需要驱动 UI 但更新频繁且非紧急?→ 用
startTransition包裹(本文规则); - 需要展示"更新中"反馈?→ 改用
useTransition的isPending(见 rendering-usetransition-loading.md); - 涉及昂贵的派生计算?→ 同时用
useMemo缓存(见 rerender-use-deferred-value.md); - 监听滚动、触摸、滚轮事件?→ 记得加
{ passive: true }(见 client-passive-event-listeners.md)。
这套组合拳的核心思想始终如一:把渲染调度权交给 React,让紧急交互插队,让非紧急更新靠边等待。startTransition是其中成本最低、侵入最小的一招——一行包裹,换来的是主线程不被高频更新占满,用户交互始终保持流畅。
- 前端
- 教程
【免费下载链接】preguntas-entrevista-react
Preguntas típicas sobre React para entrevistas de trabajo ⚛️
相关推荐
Phoenix 前端性能实践:用 React startTransition 处理非紧急更新,保持 UI 响应
Phoenix 前端性能实践:用 React startTransition 处理非紧急更新,保持 UI 响应 导读 本文聚焦 Vercel Engineeri
可观测性AI 评测LLMOpsAI 应用人工智能OpenMontage 前端性能实践:用 startTransition 处理 React 非紧急更新,保持 UI 始终响应
OpenMontage 前端性能实践:用 startTransition 处理 React 非紧急更新,保持 UI 始终响应 导读 在 OpenMontage
人工智能AI Agent音视频媒体生成工作流自动化open-agents 中 React 重渲染优化实践:用 startTransition 处理非紧急状态更新
open agents 中 React 重渲染优化实践:用 startTransition 处理非紧急状态更新 本文讲解 open agents 仓库内置 Ve
人工智能AI Agent代码智能体Agent 工作流Agent 沙箱工具调用后端前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考