React类组件防止componentDidUpdate中setState无限调用
React Redux类组件props更新时setState死循环/条件不触发问题修复
问题现象
你当前遇到两个异常表现:
componentDidUpdate里同时判断prevProps.data !== this.props.data和data存在part1、part2属性时,条件块永远无法进入- 去掉prevProps引用判断后,只要data存在有效值就执行setState,会触发无限重渲染
- 目标是在
this.props.data首次加载完成、或后续值发生变化时,仅执行一次setState更新状态
原始问题代码如下:
class SomePage extends Component { constructor(props) { super(props); this.state = { stuff: "" } } // NOTE: this.props.data comes from mapStateToProps from below componentDidMount() { console.log(this.props.data) // shows up as "undefined" } componentDidUpdate(prevProps) { console.log(this.props.data); // has the data I need if((prevProps.data !== this.props.data) && this.props.data.part1 && this.props.data.part2) { // LINE A console.log("inside if statement"); // never entered this.setState({stuff: this.props.data.part2}); // need to set state here!! } } render () { return ( {this.state.stuff} ) } const mapStateToProps = state => { return { data: state.analysisReducer.data; } } export default connect(mapStateToProps, null)(SomePage);
移除prevProps判断后无限循环的代码:
componentDidUpdate(prevProps) { console.log(this.props.data); // has the data I need if(this.props.data && this.props.data.part1 && this.props.data.part2) { // LINE B - removed the "prevProps.data !==" this.setState({stuff: this.props.data.part2}); // entered, but called infinitely } }
根因分析
首先你的原始代码存在3个基础语法错误,会直接导致运行异常:
mapStateToProps被错误定义在组件类内部,类作用域下不允许直接用const声明该方法,connect根本无法拿到正确的映射函数mapStateToProps返回的对象中data属性末尾多写了分号,属于语法错误render方法中return ( {this.state.stuff} )写法错误,括号内的{}会被解析为对象字面量,而非JSX插值,直接返回变量不需要额外加括号包裹
两个逻辑问题的核心原因:
- LINE A条件永远不触发:
prevProps.data !== this.props.data做的是引用比较,如果你的Redux reducer更新data时是直接修改原对象属性(可变更新),没有返回新的data对象引用,哪怕part1、part2后续被赋值,prevProps.data和this.props.data仍然指向同一个内存地址,引用判断永远为false。也存在另一种可能:data第一次从undefined变为对象时,part1或part2还未赋值,等part1、part2赋值完成时,data的引用已经不再变化,导致条件永远不满足。 - LINE B无限循环:
componentDidUpdate不仅在props变化时触发,组件自身state更新触发重渲染后也会执行。去掉props变化判断后,只要data存在有效值就会执行setState,setState触发重渲染,重渲染后再次进入生命周期,判断条件依然成立,就会再次执行setState形成死循环。
正确实现方案
第一步:修复基础语法错误
先把mapStateToProps移到组件外部,修正语法问题:
import React, { Component } from 'react'; import { connect } from 'react-redux'; class SomePage extends Component { constructor(props) { super(props); this.state = { stuff: "" } } componentDidMount() { console.log(this.props.data) // 首次渲染时异步数据未返回,打印undefined是正常现象 } componentDidUpdate(prevProps, prevState) { // 后续补充正确判断逻辑 } render () { // 修正返回值,不需要包裹元素可直接返回文本,也可用Fragment/容器标签包裹 return this.state.stuff; } } // 移到类外部,移除属性末尾多余分号 const mapStateToProps = state => { return { data: state.analysisReducer.data } } export default connect(mapStateToProps, null)(SomePage);
第二步:编写生命周期判断逻辑
根据你的reducer写法二选一即可:
方案1:Redux遵循不可变更新规范(官方推荐写法,所有更新返回新对象引用)
增加一层「待设置值和当前state值是否一致」的判断,避免无意义的setState,同时覆盖首次加载和后续更新场景:
componentDidUpdate(prevProps) { const { data } = this.props; // 先判断data是否为有效值 if (data && data.part1 && data.part2) { // 触发条件:1. data引用变化(首次加载/内容更新);2. 待设置的值和当前state不一致 if (prevProps.data !== data || this.state.stuff !== data.part2) { this.setState({ stuff: data.part2 }); } } }
该逻辑不会触发死循环:setState完成后再次进入生命周期时,this.state.stuff已经等于data.part2,第二个判断条件不成立,不会重复执行setState。
方案2:reducer为可变更新(无法修改reducer的妥协方案)
如果没法修改reducer的可变更新逻辑,就改用浅比较判断data内容是否变化,而非仅比较引用:
componentDidUpdate(prevProps) { const { data } = this.props; if (data && data.part1 && data.part2) { // 浅比较关键字段是否变化 const dataChanged = prevProps.data?.part1 !== data.part1 || prevProps.data?.part2 !== data.part2; if (dataChanged || this.state.stuff !== data.part2) { this.setState({ stuff: data.part2 }); } } }
最佳实践建议
如果stuff是完全由props.data派生的值,根本不需要放在组件state中维护,直接在render阶段从props取值计算即可,从根源上避免setState同步导致的死循环问题:
render() { const { data } = this.props; const stuff = data?.part2 || ""; return stuff; }
派生值直接在渲染时计算是React官方推荐的写法,不需要额外在生命周期中同步state,能大幅减少同类bug。
内容的提问来源于stack exchange,提问作者penguin
相关产品推荐
相关产品推荐

