前端工程
React 渲染模型:从状态流向理解性能边界
不再把 memo 当作默认答案;先分清 Render、Commit、状态归属与真正发生的 DOM 更新。
核心洞察:React 性能问题通常不是“渲染次数太多”,而是状态放错位置、数据引用不稳定,或把昂贵工作留在了渲染路径中。 霓虹港告警页包含设备筛选、告警列表和详情抽屉。若筛选词放在页面根组件,输入一个字符就会让整个页面函数重新执行。此时第一反应不该是给所有组件套
memo,而是问:这份状态到底被谁需要?
Render 不等于 DOM 更新
React 的 Render 阶段计算“下一棵 UI 树应是什么样”,Commit 阶段才把差异真正写入 DOM。组件函数被再次调用很正常;只要输出与已提交结果等价,实际 DOM 操作可能很少。 因此,优化目标不是消灭一切 Render,而是避免昂贵子树因为无关状态而参与计算,并确保真正昂贵的工作不在每次 Render 中重复执行。
状态应靠近使用者
筛选输入只被筛选栏使用,就留在筛选栏;抽屉开关只被列表和抽屉共享,再提升到它们最近的共同父级。把所有状态塞进页面顶层会扩大更新波及面,也会让组件依赖关系难以理解。
function AlertFilters({ onChange }: Props) { const [keyword, setKeyword] = useState(''); const commit = useDeferredValue(keyword); useEffect(() => onChange(commit), [commit, onChange]); return <input value={keyword} onChange={(event) => setKeyword(event.target.value)} />;}这里本地输入保持即时,较重的筛选结果可以延后更新。它解决的是交互优先级,而不是替代数据请求的防抖策略。
memo、useMemo、useCallback 的真实职责
memo 只在 props 浅比较稳定时才可能跳过子组件 Render;useMemo 缓存计算结果;useCallback 缓存函数引用。它们都需要成本,也会增加依赖维护负担。
当父组件每次都创建新对象、新数组或新回调时,memo 基本无效。更好的顺序是:先收窄状态范围;再保证数据结构稳定;最后用 Profiler 验证一个明确的昂贵子树是否值得记忆化。
避免派生状态
能从 props 或现有 state 计算出的值,不要再存一份 state。两份事实来源迟早不同步。列表的“当前可见条数”、格式化标题、是否为空,通常都应在 Render 中直接推导。
结语
React 的模型并不神秘:状态改变触发计算,计算决定提交。把状态放在正确边界,保持数据流单向且可追踪,性能优化就从猜测变成结构设计。
讨论与反应