React15 - React 15 中 componentWillReceiveProps 为什么会多次调用, 同时componentDidUpdate 也会多次调用?

在 React 15 中,componentWillReceivePropscomponentDidUpdate 的多次调用,本质上是同步渲染架构生命周期设计共同作用的结果。
下面分别解释它们为何会被重复调用。


1. componentWillReceiveProps 多次调用

触发条件

componentWillReceiveProps(nextProps)父组件重新渲染时被调用,无论新 props 是否真的改变(除非子组件实现了 shouldComponentUpdate 并返回 false,但该生命周期依然会被调用)。
因此,只要父组件 render 一次,子组件的 componentWillReceiveProps 就可能被调用一次。

多次调用的常见场景

① 父组件多次 setState 导致多次渲染

在 React 15 中,如果父组件在 非 React 合成事件(如 setTimeoutPromise、原生事件)中连续多次调用 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,会形成死循环:
更新 → componentDidUpdatesetState → 再次更新 → 再次 componentDidUpdate → …
React 15 会在几次后给出“超过最大更新深度”的警告,但在此之前 componentDidUpdate 会被反复调用。

即使有条件判断,若条件在每次更新后依然满足,也会造成多次调用。

③ 子组件在 componentWillReceivePropssetState 引发的二次更新

前面提到,子组件在 componentWillReceivePropssetState 会导致当前渲染结束后立即触发第二次更新。
这两次更新都会让子组件执行 rendercomponentDidUpdate,所以你会看到 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 中,componentWillReceivePropscomponentDidUpdate 的多次调用,根本原因在于同步递归渲染 + 缺少默认的更新批处理,加上开发者可能在生命周期中不经意地触发额外更新。理解这些机制后,通过优化组件继承方式(PureComponent)、谨慎处理内部 setState 以及合理利用生命周期比较逻辑,可以有效减少多余调用,提升性能。

posted @ 2026-03-25 23:01  箫笛  阅读(1)  评论(0)    收藏  举报