在 React Form 中更新 props 更改的状态

Posted

技术标签:

【中文标题】在 React Form 中更新 props 更改的状态【英文标题】:Updating state on props change in React Form 【发布时间】:2015-12-01 13:42:59 【问题描述】:

我在使用 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

【问题讨论】:

【参考方案1】:

componentWillReceiveProps 自反应 16 以来已被贬低:改用 getDerivedStateFromProps

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

React has some tips on dealing with this scenario.(注意,这是一篇旧文章,已从网络上删除。这是当前doc on component props的链接)。

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

尽可能动态计算值,以确保它们以后不会不同步并导致维护问题。

基本上,每当您将父母的props 分配给孩子的state 时,渲染方法并不总是在道具更新时调用。您必须手动调用它,使用 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 );
  

【讨论】:

从 React 16 开始不推荐使用 @dude 它还没有被弃用,您所指的只是供将来参考的提示。我引用[..]going to be deprecated in the future @poepje 它可能还没有被弃用,但按照当前标准,它被认为是不安全的,应该避免 那么,在 componentWillReceiveProps 被弃用后,新的做法应该是什么? @Boris 现在,React 团队基本上是在告诉你填饱肚子。他们为您提供了一种新方法,称为 getDerivedStateFromProps。问题是这是一个静态方法。这意味着您不能执行任何异步操作来更新状态(因为您必须立即返回新状态),也不能访问类方法或字段。您也可以使用记忆,但这并不适合每个用例。再一次,react 团队想要强制改变他们的做事方式。这是一个极其愚蠢和无能的设计决策。【参考方案2】:

显然情况正在发生变化....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
  

(above code by danburzo @ github )

【讨论】:

仅供参考,如果没有任何变化,您还需要返回 null,所以在您的 if 之后,您应该使用 return null @IlgıtYıldırım - 已经编辑了代码,因为有 4 人对您的评论投了赞成票 - 这真的有影响吗? 有一个很好的资源,深入介绍了不同的选项以及为什么你会使用getDerivedStateFromProps 或 memoization reactjs.org/blog/2018/06/07/… getDerivedStateFromProps 被强制为静态。这意味着您不能执行任何异步操作来更新状态,也不能访问类方法或字段。再一次,react 团队想要强制改变他们的做事方式。这是一个极其愚蠢和无能的设计决策。【参考方案3】:

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

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

为子组件提供key 属性可确保每当key 的值从外部发生变化时,都会重新渲染该组件。例如,

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

关于它的表现:

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

【讨论】:

关键,秘密!如上所述,在 React 16 中完美运行 键不起作用,如果它是一个对象并且你没有唯一的字符串 Key 确实适用于对象,我做到了。当然,我有一个唯一的 key 字符串。 @user3468806 如果不是带有外部引用的复杂对象,您可以使用JSON.stringify(myObject)从您的对象中派生唯一键。 即使key发生变化,它也不会触发componentDidMount【参考方案4】:

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

【讨论】:

这不是反模式吗:reactjs.org/blog/2018/06/07/…? 这正是我想要的。完美地遵循模式。【参考方案5】:

还有componentDidUpdate 可用。

函数签名:

componentDidUpdate(prevProps, prevState, snapshot)

当组件更新时,以此为契机对 DOM 进行操作。初始 render 时不会被调用。

参见You Probably Don't Need Derived State 文章,其中描述了componentDidUpdategetDerivedStateFromProps 的反模式。我发现它非常有用。

【讨论】:

我最终使用了componentDidUpdate,因为它很简单而且更适合大多数情况。 我很困惑,因为您分享的文章说不要采用这种方法使用密钥使组件无效? 见结尾,'如果每个值都有明确的真实来源,你可以避免上面提到的反模式'。它通常说,如果您最终在didUpdate 中检查propsprevProps,如果您重新考虑数据流和层次结构,可能会有更好的解决方案。【参考方案6】:

You Probably Don't Need Derived State

1.从父级设置密钥

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

2。使用getDerivedStateFromProps/componentWillReceiveProps

如果密钥由于某种原因不起作用(也许组件的初始化成本很高)

通过使用getDerivedStateFromProps,您可以重置状态的任何部分,但似乎 这个时候有点小问题(v16.7)!请参阅the link above了解用法

【讨论】:

【参考方案7】:

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

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

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

    完全受控的组件:ModalBodyParentComponent 将拥有start_time 状态。在这种情况下,这不是我更喜欢的方法,因为我认为模态应该拥有这种状态。 带有键的完全不受控制的组件:这是我更喜欢的方法。来自反应文档的示例:https://codesandbox.io/s/6v1znlxyxn。您将从ModalBody 完全拥有start_time 状态并使用getInitialState,就像您已经完成的那样。要重置start_time 状态,您只需将密钥从ParentComponent 更改为

【讨论】:

对于在 2020+ 或 React 17+ 中阅读此内容的人,请阅读链接,特别是“建议:带有密钥的完全不受控制的组件”部分,这是此处建议的选项 #2。 【参考方案8】:
// store the startTime prop in local state
const [startTime, setStartTime] = useState(props.startTime)
// 
useEffect(() => 
  if (props.startTime !== startTime) 
    setStartTime(props.startTime);
  
, [props.startTime]);

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

【讨论】:

【参考方案9】:

从他们的文档中可以清楚地看到:

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

【讨论】:

【参考方案10】:

使用备忘录

op 对 state 的推导是对 props 的直接操作,不需要真正的推导。换句话说,如果您有一个可以直接使用或转换的道具,则无需将道具存储在状态

鉴于start_time 的状态值只是start_time.format("HH:mm") 的prop,因此prop 中包含的信息本身已经足以更新组件。

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

【讨论】:

【参考方案11】:

表单的数据源必须基于用户输入,在用户输入的情况下,任何导致子组件更新的情况,都会触发componentWillReceiveProps或getDerivedStateFromProps操作,此时,比较后的值肯定是不相等的,setState执行后,用户输入的值会发生变化,这不是错误吗?

【讨论】:

【参考方案12】:

我认为使用 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 的问题,即如何处理初始状态。

以上是关于在 React Form 中更新 props 更改的状态的主要内容,如果未能解决你的问题,请参考以下文章

vue和react中props变化后修改state的方式

如何使用 props 在 React 中自动填充可编辑的 redux-form 字段?

React 生命周期函数

react父组件传入的属性没有让子组件收到的prop变化

react常见问题

React开发(124):ant design学习指南之form中的this.props.form