道具在React形式中的更新状态更改


184

我在使用React表单和正确管理状态时遇到麻烦。我有一个形式为模式的时间输入字段。初始值在中设置为状态变量getInitialState,并从父组件传递。这本身工作正常。

当我想通过父组件更新默认的start_time值时,就会出现问题。更新本身通过发生在父组件中setState start_time: new_time。但是在我的表单中,默认的start_time值从不更改,因为它仅在中定义一次getInitialState

我尝试过通过componentWillUpdate强制进行状态更改setState start_time: next_props.start_time,该更改确实有效,但给了我Uncaught RangeError: Maximum call stack size exceeded错误。

所以我的问题是,在这种情况下更新状态的正确方法是什么?我是否以某种方式在考虑这个错误?

当前代码:

@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

Answers:


287

componentWillReceiveProps被depcricated因为反应16:使用getDerivedStateFromProps代替

如果我理解正确,您有一个父组件正在传递start_time给将该ModalBody组件分配给其自身状态的组件?您想从父组件而不是子组件更新该时间。

React有一些处理这种情况的技巧。(请注意,这是一篇从网上删除的旧文章。这是指向有关props的当前文档的链接)。

使用道具生成状态getInitialState通常会导致“真相来源”重复,即真实数据所在的位置。这是因为getInitialState仅在首次创建组件时才调用。

只要有可能,就可以即时计算值,以确保以后不会出现不同步的情况并造成维护麻烦。

基本上,无论何时将父对象分配给propsstate对象,都不总是在prop更新时调用render方法。您必须使用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 });
  }
}

83
自React 16起已弃用
花花公子

7
@dude尚未弃用,您所引用的内容仅供参考,以供将来参考。我引用[..]going to be deprecated in the future
paddotk

7
@poepje可能尚未弃用,但按当前标准将其视为不安全,应避免使用
unflores

12
那么,不赞成使用componentWillReceiveProps之后的新方法应该是什么?
Boris D. Teoharov '19年

4
@Boris现在,反应团队基本上是在告诉您要塞满。它们为您提供了一个名为getDerivedStateFromProps的新方法。问题是这是静态方法。这意味着您无法执行任何异步操作来更新状态(因为必须立即返回新状态),也无法访问类方法或字段。您也可以使用备忘录,但这并不适合每个用例。反应团队再次想压制他们的处事方式。这是一个极其愚蠢且无能为力的设计决策。
ig-dev

76

显然情况正在发生变化。... getDerivedStateFromProps()现在是首选函数。

class Component extends React.Component {
  static getDerivedStateFromProps(props, current_state) {
    if (current_state.value !== props.value) {
      return {
        value: props.value,
        computed_prop: heavy_computation(props.value)
      }
    }
    return null
  }
}

(以上代码来自danburzo @ github)


7
仅供参考,您还需要恢复null,如果没有应你经过这么合适的改变,如果,你应该去return null
Ilgıt耶尔德勒姆

@IlgıtYıldırım-已编辑代码,因为4个人对您的评论进行了评论-确实有什么不同吗?
ErichBSchulz '18年

有一个非常不错的资源深入介绍了不同的选项,以及为什么要使用其中一个getDerivedStateFromProps作为
备忘录的原因reactjs.org/blog/2018/06/07/…– unflores

2
getDerivedStateFromProps被强制为静态。意味着您不能做任何异步更新状态的操作,也不能访问类方法或字段。反应团队再次想压制他们的处事方式。这是一个非常愚蠢且无能为力的设计决策。
ig-dev

39

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

如果外面有什么变化,请考虑使用完全重置子组件key

key子组件提供支持可确保无论何时key外部更改的值都会重新渲染该组件。例如,

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

关于其性能:

尽管这听起来可能很慢,但性能差异通常并不明显。如果组件具有繁琐的逻辑以在更新上运行,则使用键甚至可以更快,因为绕过该子树的差异。


1
关键,秘密!如上所述,在React 16中可以完美运行
Darren Sweeney

键是无效的,如果它是一个对象,并且您没有唯一的字符串
user3468806

键确实适用于对象,我做到了。当然,我有一个唯一的键字符串。
tsujp

@ user3468806如果它不是具有外部引用的复杂对象,则可以用来JSON.stringify(myObject)从您的对象派生唯一键。
罗伊·普林斯

24

也有componentDidUpdate可用。

功能签名:

componentDidUpdate(prevProps, prevState, snapshot)

当组件已更新时,可借此机会在DOM上进行操作。不会在initial上被调用render

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


我最终使用componentDidUpdate它,因为它很简单,并且更适合大多数情况。
KeitelDOG

14

新的挂钩方法是使用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来检查对指定道具的更改,并采取行动以在道具更改时更新状态。


5

您可能不需要派生状态

1.设置父项的密钥

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

2.使用getDerivedStateFromProps/componentWillReceiveProps

如果密钥由于某种原因而无法使用(也许该组件初始化非常昂贵)

通过使用,getDerivedStateFromProps您可以重置状态的任何部分,但此时似乎有点问题(v16.7)!,有关用法,请参见上面的链接


2

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

道具更改时的擦除状态是反模式

从React 16开始,componentWillReceiveProps被弃用。从反应文档中,在这种情况下,推荐的方法是使用

  1. 完全控制组件:在ParentComponentModalBody将自己的start_time状态。在这种情况下,这不是我的首选方法,因为我认为模态应该拥有该状态。
  2. 完全不受控制的组件,带有一个键:这是我的首选方法。react文档中的一个示例:https : //codesandbox.io/s/6v1znlxyxn。您将完全拥有自己的start_time状态ModalBody并使用getInitialState它,就像您已经做过的一样。要重置start_time状态,您只需从ParentComponent


0

使用备忘

op的状态派生是对道具的直接操纵,不需要真正的派生。换句话说,如果您拥有可以直接使用或转换的道具,则无需将道具存储在state上

假设的状态值 start_time仅是prop start_time.format("HH:mm"),则prop中包含的信息本身已经足以更新组件。

但是,如果您确实只想在更改道具时调用格式,则根据最新文档执行此操作的正确方法是通过Memoize:https ://reactjs.org/blog/2018/06/07/you-probably-dont- need-derived-state.html#what-about-memoization


-1

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

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;
        }
    }
}

我认为此响应是含糊不清的(代码很难读取,没有任何解释/与OP的问题链接),并且不能解决OP的问题,即如何处理初始状态。
netchkin
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.