【问题标题】:How to avoid the setState() / render() endless loop when passing data from child to parent?将数据从子级传递给父级时如何避免 setState() / render() 无限循环?
【发布时间】:2018-06-13 21:07:35
【问题描述】:

我试图将子数据保存为父状态,但由于setState() 调用render() 而导致无限循环。

错误消息: 超出最大更新深度。当组件在componentWillUpdatecomponentDidUpdate 内重复调用setState 时,可能会发生这种情况。 React 限制了嵌套更新的数量以防止无限循环。

对 React 来说相对较新,所以在谷歌搜索解决方案时我似乎无法说出这个问题。我知道为什么会发生错误,我只是不知道如何解决这个问题。有没有我可以使用的特定方法来防止重新渲染?

这里是父级:

export class ToDoList extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      data: null
    }
  }

  myCallback = (dataFromChild) => {
    this.setState({data: dataFromChild.toUpperCase()})
  }

  render() {
    return (
      <div>
        <ToDoItem callbackFromParent={this.myCallback}/>
      </div>
    );
  }
}

孩子:

class ToDoItem extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      listInfo: 'Doggos'
    }
  }

  render(){
    return(
      <h1>{this.props.callbackFromParent(this.state.listInfo)}</h1>
    );
  }
}

【问题讨论】:

  • 您能解释一下您要做什么吗?这段代码有很多问题。
  • 你为什么还要调用那个回调?只需在 &lt;h1&gt; 标签中显示 listInfo 即可。
  • 不仅componentWillUpdatecomponentDidUpdate不应该调用setState,而且render也不应该引起状态改变。
  • 您的代码完全按照您的要求执行。每次渲染时,它都会设置状态。这是一种反模式。在您明确您希望它做什么之前,无法确定如何最好地修复它。状态是否应该更新每个渲染?实际上应该显示什么?通过阅读您的代码,您似乎希望它做的就是您所描述的:无限循环。
  • @ChaseDeAnda 最终,我希望能够在兄弟姐妹之间共享数据,我试图通过将数据提供给作为中间人的父级来做到这一点。抱歉,不清楚,我只是在测试一些代码来查看数据流,所以这个 sn-p 实际上不应该做任何有用的事情。它应该只是用大写字母呈现数据(只是为了查看数据是否已通过父级)

标签: javascript reactjs


【解决方案1】:

您的代码正是这样做的,一个无限循环。当你的ToDoItem 组件渲染时,它会调用callbackFromParent 更新ToDoList 的状态,导致ToDoList 重新渲染,随后重新渲染ToDoItem。由于ToDoItem 重新渲染,它再次调用callbackFromParent 等等......

我想问你为什么要渲染callbackFromParent 的非返回值函数。它不返回任何东西,所以你为什么要在 &lt;h1&gt; 标签中呈现它是没有意义的。

【讨论】:

  • 我想这不是一段非常有用的代码(只是测试组件之间的数据流),但基本上我试图将它保存在父状态,以便我可以使用不同子组件中的数据。在兄弟姐妹之间共享数据
  • @Jen 通常在 React 中,我们尝试将状态仅保留在父级中,然后将部分状态传递给子级。祝你好运。
【解决方案2】:

您共享的代码存在一个小问题,即您正在从 render() 调用一个函数,而不是将其绑定到某个使其进入无限循环的事件...

【讨论】:

    【解决方案3】:

    这是你想要达到的目标吗?

    class ToDoList extends React.Component {
    
      toUpper = (dataFromChild) => {
        return dataFromChild.toUpperCase();
      }
    
      render() {
        return (
          <div>
            <ToDoItem toUpper={this.toUpper}/>
          </div>
        );
      }
    }
    
    class ToDoItem extends React.Component {
      constructor(props){
        super(props);
        this.state = {
          listInfo: 'Doggos'
        }
      }
    
      render(){
        return(
          <h1>{this.props.toUpper(this.state.listInfo)}</h1>
        );
      }
    }
    
    ReactDOM.render(<ToDoList />, document.getElementById('root'));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
    <div id="root"></div>

    由于以下原因,您会陷入无限循环:

    1) 您在每次渲染时调用父回调。

    2) 您将大写的值保存在父状态中。

    当父状态更新时,子状态会重新渲染,这意味着它会再次调用回调,这将导致重新渲染,再次调用回调等等......

    另一种解决方案是将 util 函数传递给子级,然后在重新渲染时调用它一次。由于父级中没有状态被更新,因此子级不会被重新渲染。

    【讨论】:

    • 这是使用父组件的方法作为转换,它不是在父组件中存储数据并在子组件中可视化它,因为 OP 要求“我正在尝试将子数据保存在父组件的状态”,请参阅有问题的第一行
    【解决方案4】:

    如果您尝试将数据保存在父级上但想在子级中显示,请尝试以下操作:

    这是父级:

    export class ToDoList extends React.Component {
      constructor(props){
        super(props);
        this.state = {
          data: null
        }
      }
    
      myCallback = (dataFromChild) => {
        this.setState({data: dataFromChild})
      }
    
      render() {
        return (
          <div>
            <ToDoItem callbackFromParent={this.myCallback} data={this.state.data}/>
          </div>
        );
      }
    }
    

    孩子:

    class ToDoItem extends React.Component {
      constructor(props){
        super(props);
        this.state = {
          times: 0
        }
        // Bind Explained below
        this.iBeClicked = this.iBeClicked.bind(this);
      }
    
      iBeClicked(){
        this.setState({times: ++this.props.data});
        this.props.callbackFromParent(this.props.data++);
      }
    
      render(){
        return(
          <div className="wrap">
            <h1 onClick="iBeClicked">{this.props.data !== null ? this.props.data: 'Nothing' }</h1>
          </div>
        );
      }
    }
    

    您使用 this.method.bind(this) 来将组件的 this 绑定到 React 回调在渲染中的执行。

    【讨论】:

    • 为什么要添加点击事件?
    • 因为除了从孩子传给父母之外,他还不确定他想要实现的目标。您提出的@ChaseDeAnda 是父组件转换,您在子范围内使用父回调,您没有像 OP 询问的那样保存数据“我正在尝试以父状态保存子数据”跨度>
    猜你喜欢
    • 2017-08-03
    • 2020-09-22
    • 1970-01-01
    • 1970-01-01
    • 2011-09-07
    • 2021-06-29
    • 1970-01-01
    • 2021-05-09
    • 2017-06-24
    相关资源
    最近更新 更多