React getDerivedStateFromProps的正确使用方法

3
在这个例子中。

https://codepen.io/ismail-codar/pen/QrXJgE?editors=1011

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  static getDerivedStateFromProps(nextProps, prevState) {
    console.log("nextProps", nextProps, "\nprevState", prevState)
    if(nextProps.count !== prevState.count)
      return {count: nextProps.count};
    else
      return null;
  }
  handleIncrease(e) {
    this.setState({count: this.state.count + 1})
  }
  handleDecrease(e) {
    this.setState({count: this.state.count - 1})
  }
  render() {
    return <div>
      <button onClick={this.handleIncrease.bind(this)}>+</button>
      {this.state.count}
      <button onClick={this.handleDecrease.bind(this)}>-</button>
    </div>;
  }
}

class Main extends React.Component {
  constructor(props) {
    super(props);
    this.state = { initialCount: 1 };
  }
  handleChange(e) {
    this.setState({initialCount: e.target.value})
  }
  render() {
    return <div>
      <Counter count={this.state.initialCount} />
      <hr/>
      Change initial:<input type="number" onChange={this.handleChange.bind(this)} value={this.state.initialCount} />
    </div>
  }
}

ReactDOM.render(
  <Main/>,
  document.getElementById("root")
);

期望结果: 点击“+”、“-”按钮和文本框更改必须更新计数器。
当前情况: 主组件将initialCount存储在自己的状态中,并将初始计数传递给子计数器组件。
如果从文本框触发handleChange并更新了initialCount,那么子计数器组件也会正确更新,因为getDerivedStateFromProps静态方法提供了这个功能。
但是,通过使用handleIncrease和handleDecrease方法更新局部状态来更改计数器组件中的计数值是有问题的。
问题是getDerivedStateFromProps重新触发,然后重置计数值。但我没有预料到这一点,因为计数器组件中的局部状态更新父Main组件并不会更新。UNSAFE_componentWillReceiveProps就是这样工作的。
总之,我的getDerivedStateFromProps用法不正确,或者我的场景有另外一种解决方案。
此版本 https://codepen.io/ismail-codar/pen/gzVZqm?editors=1011 与componentWillReceiveProps很好地配合使用。
2个回答

10
尝试像您这样同步状态到props是极易出错的,并会导致应用程序出现错误。实际上,即使您的在componentWillReceiveProps中使用的示例也存在错误。如果您更频繁地重新呈现父组件,则会丢失用户输入。以下是一个演示错误的演示。增加计数器,然后单击“演示错误”,它将清除计数器。但是该按钮的setState应该完全无关。这表明为什么尝试将状态与props同步是不好的想法,应该避免。相反,尝试以下方法之一:1. 您可以通过父道具使组件完全"受控"并删除本地状态。2. 或者,您可以使组件完全“不受控制”,并通过给子代不同的键来自父代时根据需要重置子状态。这两种方法都在React博客上有关避免派生状态的文章中进行了描述。博客文章包括详细的演示示例,因此强烈建议查看。

1

我不确定我是否理解正确,但如果您想将prop用作初始值的“种子”,则可以在构造函数中执行此操作,甚至不需要使用getDerivedStateFromProps。实际上,您不需要复制状态:

class Counter extends React.Component {
  render() {
    return <div>
      <button onClick={this.props.handleIncrease}>+</button>
      {this.props.count}
      <button onClick={this.props.handleDecrease}>-</button>
    </div>;
  }
}

class Main extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 1 };
  }
  handleIncrease() {
    this.setState(prevState => ({count: prevState.count + 1}))  
  }
  handleDecrease() {
    this.setState(prevState => ({count: prevState.count - 1}))  
  }
  render() {
    return (
     <div>
      <Counter count={this.state.count} />
      <hr/>
      Change initial: 
      <input 
        type="number" 
        handleIncrease={this.handleIncrease.bind(this)} 
        handleDecrease={this.handleDecrease.bind(this)} 
        count={this.state.count} 
      />
     </div>
    )
  }
}

谢谢Tomasz。你的建议是一个替代方案,但我不能在现实世界中使用它。一个组件必须使用自己的事件处理程序更新自己的状态。 - ismail codar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接