我有一个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

当前回答

来自react文档:https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

道具改变时擦除状态是一种反模式

自React 16起,componentWillReceiveProps已弃用。从react文档来看,在这种情况下推荐的方法是使用

Fully controlled component: the ParentComponent of the ModalBody will own the start_time state. This is not my prefer approach in this case since i think the modal should own this state. Fully uncontrolled component with a key: this is my prefer approach. An example from react documentation : https://codesandbox.io/s/6v1znlxyxn . You would fully own the start_time state from your ModalBody and use getInitialState just like you have already done. To reset the start_time state, you simply change the key from the ParentComponent

其他回答

我使用功能组件和useEffect钩子提出了以下解决方案: 它通过使用useEffect钩子监视props中的控制属性来工作

const { useEffect, useState } = React const Child = (props) => { const [bgColor, setBgColor] = useState(props.bgColor); const { children } = props; useEffect(() => { setBgColor(props.bgColor); }, [props.bgColor]); return ( <div style={{ height: "100px", width: "100px", backgroundColor: bgColor }}>{children}</div> ) } const Parent = (props) => { const [childControllingProp, setChildControllingProp] = useState(props.childControllingProp); const { title } = props; const inputRef = React.createRef(); return ( <> <input ref={inputRef} type="text" onChange={() => setChildControllingProp(inputRef.current.value)}/> <Child bgColor={childControllingProp}>{title}</Child> </> ) } $(document).ready(() => { ReactDOM.render( <Parent title="Title" childControllingProp="blue"/>, document.querySelector("#root") ); }) <script src="https://unpkg.com/react@16/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <div id="root"></div>

你可能不需要派生状态

1. 从父节点设置一个键

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

2. 使用getDerivedStateFromProps / componentWillReceiveProps

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

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

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

这个方法可以迁移到类组件吗?

使用Memoize的

op对状态的推导是对道具的直接操作,不需要真正的推导。换句话说,如果你有一个可以直接使用或转换的道具,就没有必要在状态上存储道具。

假设start_time的状态值只是道具start_time.format("HH:mm"),道具中包含的信息本身就足以更新组件。

然而,如果你只想在道具更改时调用format,正确的方法是通过Memoize: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#what-about-memoization

来自react文档:https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

道具改变时擦除状态是一种反模式

自React 16起,componentWillReceiveProps已弃用。从react文档来看,在这种情况下推荐的方法是使用

Fully controlled component: the ParentComponent of the ModalBody will own the start_time state. This is not my prefer approach in this case since i think the modal should own this state. Fully uncontrolled component with a key: this is my prefer approach. An example from react documentation : https://codesandbox.io/s/6v1znlxyxn . You would fully own the start_time state from your ModalBody and use getInitialState just like you have already done. To reset the start_time state, you simply change the key from the ParentComponent