React中state值未变化时组件仍重渲染的原因解析
核心测试代码
import { useState } from "react"; export default function App() { console.log("rendered...") const [count, setCount] = useState(0); const increaseCount = () => { setCount(count + 1); }; const setFourValue = () => { setCount(4); }; return ( <div className="App"> <div style={{ display: "flex", alignItems: "center", columnGap: ".25rem" }} > <button onClick={increaseCount}>Increase</button> <button onClick={setFourValue}>Set 4</button> <span>{count}</span> </div> </div> ); }
现象产生的根本原因
这个表现是React状态更新调度逻辑的固有设计,和值比较规则、渲染跳出(bailout)的触发时机直接相关:
- React调用
setState时不会立刻对比新旧值决定是否跳过渲染,所有状态更新会先进入调度队列。新旧状态的相等判断用Object.is实现,只有判定完全相等时才有机会跳过渲染。 - 当你通过Increase按钮把count累加到4时,最后一次状态更新是
setCount(4),这次更新完成组件重渲染后,组件闭包内捕获的count值、React记录的当前组件状态值都为4。 - 第一次点击Set 4触发
setCount(4)时,React虽然对比发现新旧值相等,但不会直接跳出渲染:它会执行一次组件的render函数做校验,遍历检查组件子树是否存在待处理的更新、上下文变化等需要触发更新的场景,这次校验不会产生实际的DOM修改,只是走一遍render流程确认无变更,所以你会看到控制台打印rendered...。 - 等这次校验渲染完成后,React会记录当前count值已经稳定为4,后续再点击Set 4触发同值更新时,React在调度阶段就会判定新旧值完全一致且已经完成过校验,直接触发bailout逻辑,完全跳过组件及子树的render流程,也就不会再打印渲染日志。
补充说明:这个机制只针对组件完成一次渲染后,首次触发的同值状态更新生效;如果同值更新是在已经被判定跳过的更新队列里重复触发,React会直接合并丢弃,不会重复执行校验渲染。
内容的提问来源于stack exchange,提问作者Umut Palabiyik
相关产品推荐
相关产品推荐

