Patch过程中组件是否复用取决于类型和key:同类型且key相同时复用实例;类型不同或key不同时卸载重建;key缺失或动态生成易致错位与无效重建。

Patch 过程中组件是否复用,取决于类型和 key
React 的 patch 阶段发生在 diff 之后,作用是把虚拟 DOM 的差异应用到真实 DOM 上。这个阶段本身不决定组件要不要重新渲染,而是执行“如何更新”的指令。真正触发组件函数体再次执行(即重渲染)的,是更上游的条件——比如 props 引用变化、state 更新或父组件重渲染。
但 patch 过程会影响组件实例是否被复用:
- 同类型 + 相同 key:React 复用现有组件实例(类组件保留 this,函数组件保留 hooks 链),只更新 props/state;
- 类型不同(如 div → span)或 key 不同:直接卸载旧组件、挂载新组件,相当于一次完整重建;
- key 缺失或动态生成(如 index):容易导致列表项错位,patch 时无法稳定匹配,引发无谓的销毁与重建。
真正触发重新渲染的四个核心条件
组件函数体被执行(即“重渲染”发生),只由以下任一条件驱动,和 patch 无关:
- 组件自身调用了 setState 或 useState 的 setter(哪怕新旧值相等,也会触发);
- 父组件重渲染,且传给它的 props 引用发生变化(浅比较 !==);
- 它订阅了 Context,而该 Context.Provider 的 value 引用变了;
- 它使用了自定义 Hook,而该 Hook 内部触发了状态更新或强制刷新(如 useReducer + dispatch)。
为什么“对象/函数 props”会悄悄触发重渲染
这是最常被忽略的根源。例如:
function Parent() {
const [count, setCount] = useState(0);
const handleClick = () => setCount(c => c + 1);
// 每次渲染都新建对象和函数
return <Child data={{ id: 1 }} onClick={() => handleClick()} />;
}
即使 data 内容没变,{ id: 1 } 是新对象引用;() => handleClick() 是新函数引用。子组件接收到“新引用”,React.memo 浅比较失败,于是重渲染。
解决方法很明确:
- 用
useMemo缓存对象/数组 props; - 用
useCallback缓存函数 props; - 确保 Context.Provider 的 value 是稳定引用(也建议用 useMemo 包裹)。
Context 和 memo 的配合要点
React.memo 只拦截 props 变化,对 Context 无感。如果子组件读取 Context,而 Context 值变了,它仍会重渲染——哪怕 props 完全没动。
所以:
- 不要指望 memo 能挡掉 Context 更新带来的渲染;
- 若子组件只依赖 Context 中某几个字段,可用
useContext+useMemo拆解并缓存派生值; - 对高频率更新的 Context(如全局主题、用户权限),考虑拆分为多个细粒度 Context,避免“牵一发而动全身”。


















