React类组件与函数组件重渲染行为及纯组件特性问题咨询
问题1:纯组件定义表述准确性
你提到的「当待更新的状态值与前序状态值完全相同时,纯组件不会触发重渲染」的表述不准确,属于对纯组件特性的片面解读。
React生态中纯组件的核心定义分为两层:
- 类组件维度:继承
React.PureComponent的类组件,会自动对props、state、context做浅比较,若浅比较结果为相等,直接跳过本次组件重渲染流程 - 通用概念维度:纯组件本质是符合函数式编程规则的组件——相同输入必然产生相同输出、执行过程无副作用。跳过相同值触发的重渲染只是React为纯组件提供的性能优化手段,并非纯组件的定义本身。
需要注意,纯组件做的是浅比较而非深比较:如果状态是引用类型,哪怕内部属性完全一致,只要引用地址发生变化,浅比较就会判定为不相等,触发重渲染;反过来如果引用地址未变,哪怕内部属性被直接修改,浅比较也会判定为相等,跳过渲染。
问题2:函数组件默认是否为纯组件
「函数组件默认表现出纯组件特征、传入相同状态更新值就不会重渲染」的结论不成立,你第一个测试场景的现象只是React状态更新的基础兜底优化,并非函数组件自带的纯组件特性。
第一个测试的核心代码如下:
const Home = () => { const [count, setCount] = useState(1) const handleCount = () => { setCount(1) } console.log(`HOME ${count} - Functional Component`); return ( <div> <button onClick={handleCount}>Click Me</button> <div>{count + 1}</div> </div> ) } export default Home
测试现象:点击按钮时控制台无新增打印日志,组件未触发重渲染。
这个场景下点击不触发重渲染的本质是:React 18+ 对所有组件(包含类组件、函数组件,无论是否为纯组件)的状态更新都做了基础兜底:如果调用setState传入的新状态和当前状态用Object.is()比较结果为相等,React会默认将该组件从本次更新队列中移除。
该行为和纯组件特性有本质区别:
- 纯组件的比较范围覆盖
props、state、context全量输入,而默认未做优化的函数组件,只要父组件触发重渲染,哪怕传给它的props完全没有变化,它也会跟随父组件重渲染——这是典型的非纯组件特征。如果要让函数组件获得纯组件的浅比较跳过渲染能力,需要手动使用React.memo()包裹组件。 - 这个基础兜底退出逻辑存在明确的例外场景,也就是你第三个问题遇到的特殊表现。
问题3:相同状态值下仍触发重渲染的原因
你遇到的第二次点击仍触发重渲染的现象,是React 18自动批处理+更新退出逻辑的既定规则导致,并非逻辑bug。
第二个测试的核心代码如下:
const [count, setCount] = useState(0) const handleCount = () => { setCount(1) }
测试现象:首次点击按钮组件重渲染,count从0变为1;第二次点击按钮时控制台仍新增打印日志,组件再次触发重渲染。
该场景的完整执行逻辑如下:
- 首次点击按钮:当前count值为0,传入的新值为1,
Object.is(0, 1)比较结果为false,React正常调度重渲染,组件执行完成后count更新为1,完成第一次渲染。 - 第二次点击按钮:当前count值为1,传入的新值为1,按照基础退出逻辑本应跳过渲染,但React存在特殊兼容规则:如果组件上一次渲染是由状态更新触发,且本次更新是在原生事件回调、定时器、Promise回调这类非React直接调度的上下文中触发的同值更新,React不会立刻做相等判断退出,而是会先执行一次组件渲染流程,在渲染阶段计算完新状态后再做比较,如果确认值没有变化,就不会提交DOM更新,但组件函数本身会执行一次,也就是你看到的控制台打印日志现象。
- 第三次及之后点击:因为上一次渲染已经走了同值退出流程,后续再触发完全相同的同值更新时,React就会直接跳过,不会再执行组件函数。
这个设计是React为了兼容旧版本逻辑、避免边缘场景下更新丢失做的妥协,并非纯组件判断逻辑失效。
问题4:组件重渲染的触发时机
组件重渲染的调度触发时机是调用setState且React判定该更新需要进入调度队列的时刻,但组件函数实际执行、DOM实际更新的时机,是React调度器处理完对应优先级任务、完成状态计算之后的时刻,并非调用setState的同步时刻。
完整的更新流程可以简化为:
- 调用
setState→ React做第一层Object.is相等判断,如果值相等直接退出,不触发任何调度 - 如果判断值不相等,将更新加入对应优先级的调度队列 → 等调度器轮到该优先级任务 → 执行组件函数计算新的虚拟DOM → 做虚拟DOM差异比对 → 将差异提交到真实DOM完成更新
你在组件函数中写的console.log会在组件函数执行阶段触发,这个阶段晚于setState调用时刻,早于DOM提交时刻。
问题5:同值setState的执行逻辑
初始状态为true时调用setState(true)是否走更新流程,需要分场景判断:
- 如果是组件挂载后第一次触发该状态的同值更新,且更新发生在非React调度的上下文(比如原生事件回调、手动创建的定时器、原生Promise回调):React会进入更新调度流程,执行一次组件渲染函数,但不会提交DOM更新,在渲染阶段确认值确实无变化后就终止后续流程。
- 如果已经触发过一次同值更新后再重复触发同值更新,或者更新发生在React直接调度的上下文(比如
useEffect执行过程、组件渲染函数内部、React合成事件的批处理流程中):React会直接用Object.is比较新旧值,结果相等就直接跳过整个更新流程,既不会执行组件渲染,也不会走后续DOM更新逻辑。
内容的提问来源于stack exchange,提问作者Zain Shabir

