React15 - React 15 中 componentWillReceiveProps 为什么会多次调用, 同时componentDidUpdate 也会多次调用?
在 React 15 中,componentWillReceiveProps 和 componentDidUpdate 的多次调用,本质上是同步渲染架构和生命周期设计共同作用的结果。
下面分别解释它们为何会被重复调用。
1. componentWillReceiveProps 多次调用
触发条件
componentWillReceiveProps(nextProps) 在父组件重新渲染时被调用,无论新 props 是否真的改变(除非子组件实现了 shouldComponentUpdate 并返回 false,但该生命周期依然会被调用)。
因此,只要父组件 render 一次,子组件的 componentWillReceiveProps 就可能被调用一次。
多次调用的常见场景
① 父组件多次 setState 导致多次渲染
在 React 15 中,如果父组件在 非 React 合成事件(如 setTimeout、Promise、原生事件)中连续多次调用 setState,每次都会触发一次完整的渲染流程:
// 父组件中
setTimeout(() => {
this.setState({ a: 1 }); // 父组件 render 一次
this.setState({ b: 2 }); // 父组件再 render 一次
}, 0);
父组件每 render 一次,子组件的 componentWillReceiveProps 就会被调用一次。
② 父组件在生命周期中触发额外更新
例如父组件在 componentDidMount 中调用 setState,导致一次额外渲染;如果子组件又在该钩子中改变自身状态,可能引发连锁反应。
③ componentWillReceiveProps 内部调用 setState
这是 React 15 中一个经典“陷阱”。
当你在 componentWillReceiveProps 中调用 this.setState 时,React 会在当前渲染流程结束后立即调度一次新的更新,从而导致:
- 子组件先经历当前渲染(
render+componentDidUpdate); - 然后立即发起第二次更新(
componentWillReceiveProps可能再次被调用,如果父组件在此期间没有重新渲染,则不会;但子组件的状态更新会触发自身重新渲染,从而再次调用componentDidUpdate)。
这种情况下,componentWillReceiveProps 可能不会自身重复调用,但配合父组件的频繁渲染,依然容易出现多次。
④ 未做浅比较或纯组件优化
默认情况下,父组件 render 时,子组件的 componentWillReceiveProps 一定会被调用。若父组件本身因状态频繁变化而高频渲染,子组件的该生命周期就会高频触发。
2. componentDidUpdate 多次调用
componentDidUpdate(prevProps, prevState) 在每次渲染完成后被调用。
它的多次调用通常源于多次完整的更新流程,或者在一个更新流程中触发了新的更新。
常见原因
① 父组件多次渲染导致子组件多次更新
和上面一样,父组件每完成一次 render,子组件就会完成一次更新(除非 shouldComponentUpdate 阻止),因此 componentDidUpdate 会被调用同样次数。
② componentDidUpdate 内部调用 setState
如果在 componentDidUpdate 中无条件调用 setState,会形成死循环:
更新 → componentDidUpdate → setState → 再次更新 → 再次 componentDidUpdate → …
React 15 会在几次后给出“超过最大更新深度”的警告,但在此之前 componentDidUpdate 会被反复调用。
即使有条件判断,若条件在每次更新后依然满足,也会造成多次调用。
③ 子组件在 componentWillReceiveProps 中 setState 引发的二次更新
前面提到,子组件在 componentWillReceiveProps 中 setState 会导致当前渲染结束后立即触发第二次更新。
这两次更新都会让子组件执行 render 和 componentDidUpdate,所以你会看到 componentDidUpdate 被调用了两次(一次来自父组件触发,一次来自内部状态变化触发)。
④ 兄弟组件或祖先组件的连锁更新
React 15 的渲染是深度优先的。当多个组件相互依赖,在一个更新周期中可能触发多个 setState,导致一次用户操作引发多次完整的渲染树更新,每个组件的 componentDidUpdate 也会相应执行多次。
3. 如何避免不必要的多次调用
- 使用
React.PureComponent:对 props 和 state 进行浅比较,避免因父组件无意义重绘而触发子组件更新。 - 在
componentWillReceiveProps中合理比较新旧 props:只有当相关 props 变化时才调用setState,避免产生额外更新。 - 避免在
componentDidUpdate中无条件调用setState。 - 将异步操作(如
setTimeout)中的多个setState合并为一个:可以利用unstable_batchedUpdates(React 15 内部存在,但不暴露给用户,通常无法直接使用),或者将状态聚合后一次性更新。 - 使用
shouldComponentUpdate手动控制更新时机,减少不必要的渲染。
4. React 16+ 的变化
从 React 16.3 开始,componentWillReceiveProps 被标记为不安全,并逐步被 static getDerivedStateFromProps 替代;componentDidUpdate 依然存在,但由于 Fiber 架构引入了可中断渲染和自动批处理,在合成事件和生命周期中的多次 setState 会被合并为一次更新,从而大大减少了不必要的重复调用。
总结
在 React 15 中,componentWillReceiveProps 和 componentDidUpdate 的多次调用,根本原因在于同步递归渲染 + 缺少默认的更新批处理,加上开发者可能在生命周期中不经意地触发额外更新。理解这些机制后,通过优化组件继承方式(PureComponent)、谨慎处理内部 setState 以及合理利用生命周期比较逻辑,可以有效减少多余调用,提升性能。

浙公网安备 33010602011771号