React中无状态/Props变更的组件为何会重渲染?
为什么父组件重渲染时子组件会跟着重渲染?
React的默认渲染机制是:当父组件的state或props发生变化触发重渲染时,所有直接或间接的子组件都会无条件重渲染,不管子组件自身的props或state有没有变化。
这种设计是出于性能权衡的考虑:对于大多数简单组件来说,重渲染的开销极低(只是重新计算JSX并进行虚拟DOM对比,最终不会产生真实DOM更新),而额外添加props/state变更检查的成本反而更高。React选择了更通用、更简单的默认策略,把优化的控制权交给开发者。
你的临时方案为什么有效?
你用useMemo(() => <StaticComponent/>, [])能阻止子组件重渲染,是因为useMemo会缓存回调函数返回的组件元素。当依赖数组为空时,这个回调只会在首次渲染时执行一次,后续父组件重渲染时会直接复用缓存的元素,不会重新创建StaticComponent实例,因此子组件不会触发重渲染。
但要注意,这不是useMemo的正确使用场景——useMemo的设计目的是缓存昂贵的计算结果,而非组件元素。虽然能工作,但语义上不合适,也容易让其他开发者误解代码意图。
正确的优化方案:使用React.memo
对于无状态(没有自身state)且props不会变化的组件,正确的做法是用React.memo包裹,它是React专门提供的用于优化组件重渲染的高阶组件:
import { useState, memo } from 'react'; // 用memo包裹StaticComponent const StaticComponent = memo(() => { console.log('re-rendering static component (why?)') return <i>Nothing non-static in here</i> }) function App() { console.log('re-rendering App') const [state, setState] = useState(0) return ( <> {state}<br /> <button onClick={() => setState((state) => state + 1)}>+</button><br /> <StaticComponent/> </> ) } export default App
React.memo会对组件的props进行浅比较:如果前后props没有变化,就跳过组件的重渲染。对于像StaticComponent这种没有接收props的组件,浅比较永远会判定为“无变化”,因此只会在首次渲染时执行一次,后续父组件重渲染时不会触发它的重渲染。
为什么React不默认实现这种检查?
主要有两个原因:
- 性能权衡:对于复杂组件或带有深层嵌套props的组件,浅比较props需要遍历props对象,这会带来额外的性能开销。React默认选择了“优先保证简单场景的性能”,让开发者在需要优化时手动添加
memo。 - 语义清晰:默认重渲染符合“父组件更新,子组件同步更新”的直观逻辑,避免隐式优化带来的困惑。如果默认开启props检查,开发者可能会遇到“props深层变化但组件没更新”的问题(因为浅比较无法检测深层对象变化),反而增加调试成本。
内容的提问来源于stack exchange,提问作者Alexey_Chebotarev

