我有一个React表单和正确管理状态的问题。我有一个表单(模态)的时间输入字段。初始值被设置为getInitialState中的状态变量,并从父组件传入。这本身就很好。

当我想通过父组件更新默认的start_time值时,问题就来了。更新本身在父组件中通过setState start_time: new_time进行。然而,在我的表单中,默认的start_time值从未改变,因为它只在getInitialState中定义了一次。

我尝试使用componentWillUpdate强制通过setState start_time: next_props改变状态。start_time,这实际上是工作的,但它给了我Uncaught RangeError:最大调用堆栈大小超过错误。

我的问题是,在这种情况下,更新状态的正确方法是什么?我是不是想错了?

当前代码:

@ModalBody = React.createClass
  getInitialState: ->
    start_time: @props.start_time.format("HH:mm")

  #works but takes long and causes:
  #"Uncaught RangeError: Maximum call stack size exceeded"
  componentWillUpdate: (next_props, next_state) ->
    @setState(start_time: next_props.start_time.format("HH:mm"))

  fieldChanged: (fieldName, event) ->
    stateUpdate = {}
    stateUpdate[fieldName] = event.target.value
    @setState(stateUpdate)

  render: ->
    React.DOM.div
      className: "modal-body"
      React.DOM.form null,
        React.createElement FormLabelInputField,
          type: "time"
          id: "start_time"
          label_name: "Start Time"
          value: @state.start_time
          onChange: @fieldChanged.bind(null, "start_time")

@FormLabelInputField = React.createClass
  render: ->
    React.DOM.div
      className: "form-group"
      React.DOM.label
        htmlFor: @props.id
        @props.label_name + ": "
      React.DOM.input
        className: "form-control"
        type: @props.type
        id: @props.id
        value: @props.value
        onChange: @props.onChange

当前回答

还有componentDidUpdate可用。

函数signatur:

componentDidUpdate(prevProps, prevState, snapshot)

将此作为更新组件时对DOM进行操作的机会。在初始渲染时不会被调用。

参见您可能不需要派生状态文章,该文章描述了componentDidUpdate和getDerivedStateFromProps的反模式。我发现它很有用。

其他回答

你可能不需要派生状态

1. 从父节点设置一个键

当一个键发生变化时,React会创建一个新的组件实例 而不是更新当前的。键通常用于动态列表 但在这里也很有用。

2. 使用getDerivedStateFromProps / componentWillReceiveProps

如果key因为某些原因不起作用(可能组件初始化的代价非常大)

通过使用getDerivedStateFromProps,你可以重置状态的任何部分,但它似乎 这个时候有点bug (v16.7)!,参见上面的链接了解用法

componentWillReceiveProps已弃用,因为使用它“经常会导致错误和不一致”。

如果外部发生了变化,请考虑使用key完全重置子组件。

为子组件提供键道具可以确保每当key的值从外部改变时,该组件都会被重新呈现。例如,

<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>

关于其性能:

虽然这听起来很慢,但性能差异通常是微不足道的。如果组件具有运行在更新上的繁重逻辑,则使用键甚至可以更快,因为该子树的差分被绕过了。

我认为使用参考对我来说是安全的,不需要关心上面的一些方法。

class Company extends XComponent {
    constructor(props) {
        super(props);
        this.data = {};
    }
    fetchData(data) {
        this.resetState(data);
    }
    render() {
        return (
            <Input ref={c => this.data['name'] = c} type="text" className="form-control" />
        );
    }
}
class XComponent extends Component {
    resetState(obj) {
        for (var property in obj) {
            if (obj.hasOwnProperty(property) && typeof this.data[property] !== 'undefined') {
                if ( obj[property] !== this.data[property].state.value )
                    this.data[property].setState({value: obj[property]});
                else continue;
            }
            continue;
        }
    }
}

他们的医生说得很清楚:

If you used componentWillReceiveProps for re-computing some data only when a prop changes, use a memoization helper instead.

使用:https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html what-about-memoization

钩子的新方法是使用useEffect而不是旧的componentWillReceiveProps:

componentWillReceiveProps(nextProps) {
  // You don't have to do this check first, but it can help prevent an unneeded render
  if (nextProps.startTime !== this.state.startTime) {
    this.setState({ startTime: nextProps.startTime });
  }
}

在一个功能性的钩子驱动组件中变成如下:

// store the startTime prop in local state
const [startTime, setStartTime] = useState(props.startTime)
// 
useEffect(() => {
  if (props.startTime !== startTime) {
    setStartTime(props.startTime);
  }
}, [props.startTime]);

我们使用setState设置状态,使用useEffect检查对指定道具的更改,并在道具更改时执行更新状态的操作。