【问题标题】:React error Maximum update depth exceeded反应错误超过最大更新深度
【发布时间】:2021-05-15 15:15:30
【问题描述】:

我正在使用 React 进行我的前几个实验,在这个组件中,我正在调用一个外部 API 来获取所有 NBA 球员的列表,通过作为组件的 prop 接收的 teamId 过滤他们,最后渲染被过滤的玩家。

一个考虑因素是,由于我调用 API 并获得了一个大列表,因此我将其保持在组件的状态中,以便对该组件的新调用将使用该状态,而不是再次调用 API。这不是生产代码,我不拥有 API,所以我这样做是因为我收到了“请求太多”的消息,因为我一直在尝试一些东西。

无论如何,当我尝试这段代码时,我得到了已经很有名的:

已超过最大更新深度。这可能发生在组件 在 componentWillUpdate 内重复调用 setState 或 组件更新。 React 将嵌套更新的数量限制为 防止无限循环。

我已经查看了标记,我认为我没有进行任何会导致渲染方法再次触发的方法调用等等,所以我不知道为什么会发生这种情况。

提前感谢您的帮助。

这是有问题的代码:

 class Players extends Component {
    nbaPlayersUrl = "https://someUrl.com";
    state = {
        players: null,
        selectedTeamPlayers: null
    };

    render() {
        if (this.props.teamId === null) return null;

        if (this.state.players !== null) {
            var selectedTeamPlayers = this.filterPlayersByTeamId(this.state.players);
            var markup = this.getMarkup(selectedTeamPlayers);
            this.setState({selectedTeamPlayers: markup});
        } else {
            this.getPlayersList();
        }

        return (
            this.state.selectedTeamPlayers
        );
    }

    getPlayersList() {
        let api = new ExternalApi();
        let that = this;

        api.get(this.nbaPlayersUrl).then(r => {
            r.json().then(result => {
                let players = result.data.map(p => ({
                    id: p.id,
                    firstName: p.first_name,
                    lastName: p.last_name,
                    position: p.position,
                    heightInches: p.height_inches,
                    heightFeet: p.height_feet,
                    weightPounds: p.weight_pounds,
                    teamId: p.team.id
                }));

                that.setState({players: players});
                var selectedTeamPlayers = that.filterPlayersByTeamId(players);
                var markup = that.getMarkup(selectedTeamPlayers);
                that.setState({selectedTeamPlayers: markup});
            });
        });
    }

    filterPlayersByTeamId(players) {
        return players.filter(p => {
            return p.teamId === this.props.teamId;
        });
    }

    getMarkup(players) {
        var result = players.map(p => {
            <li key={p.id}>
                <div>
                    <label htmlFor="firstName">First Name</label> <input type="text" name="firstName" value={p.firstName} readOnly></input>
                </div>
                <div>
                    <label htmlFor="lastName">Last Name</label> <input type="text" name="lastName" value={p.lastName} readOnly></input>
                </div>
                <div>
                    <label htmlFor="position">Position</label> <input type="text" name="position" value={p.position} readOnly></input>
                </div>
            </li>
        });

        return (
            <ul>
                {result}
            </ul>
        );
    }
}

export default Players;

【问题讨论】:

  • 不应该将 getPlayersList() 写成异步回调吗?

标签: javascript reactjs


【解决方案1】:

@Sergio Romero - 您不能在渲染函数中设置状态,因为该设置状态将调用新渲染,该渲染将再次设置状态并调用新渲染,并生成无限循环。您加载的数据处于渲染和设置状态,这也在创建无限循环。您需要完全重写您的渲染以仅作为状态和道具的视图(它不应该操纵或加载数据)。我想你想要的更像是这样的:

class Players extends Component {
    nbaPlayersUrl = "https://someUrl.com";
    static propTypes = {
        teamId: PropTypes.number
    };
    static defaultProps = {
        teamId: null
    };
    constructor(props) {
        super(props);
        this.state = {
            players: null
        };
    }
    componentDidMount() {
        this.getPlayerList();
    }
    filterPlayersByTeamId(players, teamId) {
        return players.filter(p => {
            return p.teamId === teamId;
        });
    }
    getPlayersList = () => {
        let api = new ExternalApi();

        api.get(this.nbaPlayersUrl).then(r => {
            r.json().then(result => {
                let players = result.data.map(p => ({
                    id: p.id,
                    firstName: p.first_name,
                    lastName: p.last_name,
                    position: p.position,
                    heightInches: p.height_inches,
                    heightFeet: p.height_feet,
                    weightPounds: p.weight_pounds,
                    teamId: p.team.id
                }));

                this.setState({players});
            });
        });
    };
    render() {
        if (!this.props.teamId || !this.state.players) return null;

        const selectedTeamPlayers = this.filterPlayersByTeamId(this.state.players, this.props.teamId);

        return (
            <ul>
                {
                    selectedTeamPlayers.map(player => {
                        <li key={player.id}>
                            <div>
                                <label htmlFor="firstName">First Name</label><input type="text" name="firstName" value={player.firstName} readOnly></input>
                            </div>
                            <div>
                                <label htmlFor="lastName">Last Name</label><input type="text" name="lastName" value={player.lastName} readOnly></input>
                            </div>
                            <div>
                                <label htmlFor="position">Position</label><input type="text" name="position" value={player.position} readOnly></input>
                            </div>
                        </li>
                    })
                }
            </ul>
        );
    }
}

export default Players;

【讨论】:

    【解决方案2】:

    如果 State 和 Props 改变了 Component 将会重新渲染。 在你的 render() 函数中:

     if (this.state.players !== null) {
          var selectedTeamPlayers = this.filterPlayersByTeamId(this.state.players);
          var markup = this.getMarkup(selectedTeamPlayers);
          // this.setState({selectedTeamPlayers: 
     }
    

    尝试更改注释行,每次玩家不为空时,组件状态都会更新,因此组件渲染功能将再次运行。

    【讨论】:

      【解决方案3】:

      由于我们不能在render函数内部设置状态,因为它会产生副作用,所以你不能在render函数内部调用getPlayersList()。

      @Jason Bellomy 提到的解决方案是通过在 componentDidMount 内部调用 getPlayerList 来解决此问题的正确方法,因为它在组件插入树后立即调用,因此它是设置初始数据以呈现页面的地方.

      class Players extends Component {
          nbaPlayersUrl = "https://someUrl.com";
          state = {
              players: null,
              selectedTeamPlayers: null,
          };
          
          componentDidMount(){
           this.getPlayersList();
          }
      
          render() {
              if (this.props.teamId === null) return null;
      
              if (this.state.players !== null && this.state.selectedTeamPlayers !== null) {
                   return this.getMarkup(selectedTeamPlayers);
              } else {
                 return (<span> Loading ... </span>);
              }
          }
      
          getPlayersList() {
              let api = new ExternalApi();
              let that = this;
      
              api.get(this.nbaPlayersUrl).then(r => {
                  r.json().then(result => {
                      let players = result.data.map(p => ({
                          id: p.id,
                          firstName: p.first_name,
                          lastName: p.last_name,
                          position: p.position,
                          heightInches: p.height_inches,
                          heightFeet: p.height_feet,
                          weightPounds: p.weight_pounds,
                          teamId: p.team.id
                      }));
                     
                       var selectedTeamPlayers = that.filterPlayersByTeamId(players);
                   
                      that.setState({
                            players: players, 
                            selectedTeamPlayers: selectedTeamPlayers, 
                      });
                  });
              });
          }
      
          filterPlayersByTeamId(players) {
              return players.filter(p => {
                  return p.teamId === this.props.teamId;
              });
          }
      
          getMarkup(players) {
              var result = players.map(p => {
                  <li key={p.id}>
                      <div>
                          <label htmlFor="firstName">First Name</label> <input type="text" name="firstName" value={p.firstName} readOnly></input>
                      </div>
                      <div>
                          <label htmlFor="lastName">Last Name</label> <input type="text" name="lastName" value={p.lastName} readOnly></input>
                      </div>
                      <div>
                          <label htmlFor="position">Position</label> <input type="text" name="position" value={p.position} readOnly></input>
                      </div>
                  </li>
              });
      
              return (
                  <ul>
                      {result}
                  </ul>
              );
          }
      }
      
      export default Players;

      【讨论】:

      • 没有。您仍在渲染中设置状态,这是一个很大的 NO NO。这会导致无限循环。
      • 感谢您的提及,我很困惑,使用该条件会将重新渲染的次数减少到 1,但正如您所说,它已经禁止在渲染中使用 setState()。我也编辑了我的答案。
      • 你仍然在你的渲染中调用 getPlayerList() ,它也会设置状态。即使它不是直接的 setState 调用,执行的函数仍然可以。我添加了一个答案,其中既不设置状态也不调用在渲染中设置状态的函数。请参考我上面的回答。
      • @niima - 你越来越近了,但是,现在你将 JSX 置于状态。你永远不应该将 JSX 置于状态 stackoverflow.com/a/60418158/5419358
      • 由于 React 是一种声明性语言,正确的方法是告诉 Render,我需要您在屏幕上渲染并显示这些玩家(您的状态数据,即玩家)。渲染函数应该是这样的,好吧。我有这个玩家列表,这就是我渲染它们的方式(这将是底部返回的 JSX)。这遵循声明性模式。通过将 JSX 存储在状态中,您现在可以更改 JSX 并更改其状态并告诉渲染器如何渲染数据,这比声明式编程更具命令性。
      猜你喜欢
      • 2021-06-20
      • 1970-01-01
      • 2021-10-13
      • 2021-09-06
      • 1970-01-01
      • 2021-07-12
      • 2019-02-14
      • 2019-07-17
      • 1970-01-01
      相关资源
      最近更新 更多