道具更新状态在React Form中发生变化

我有一个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
349251 次浏览

componentWillReceiveProps在react 16中被描述:使用getDerivedStateFromProps代替

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

React提供了一些处理这种情况的技巧。(注意,这是一篇旧文章,已从网上删除。这里是到当前关于组件道具的文档的链接)。

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

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

还有componentDidUpdate可用。

函数signatur:

componentDidUpdate(prevProps, prevState, snapshot)

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

参见你可能不需要派生状态文章,其中描述了componentDidUpdategetDerivedStateFromProps的反模式。我发现它很有用。

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

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

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

  1. 完全受控组件:ModalBodyParentComponent将拥有start_time状态。这不是我更喜欢的方法在这种情况下,因为我认为模式应该拥有这种状态。
  2. 完全不受控制的组件与一个关键:这是我更喜欢的方法。一个来自react文档的例子:https://codesandbox.io/s/6v1znlxyxn。你将从你的ModalBody中完全拥有start_time状态,并像你已经做的那样使用getInitialState。要重置start_time状态,只需从ParentComponent更改键

他们的医生说得很清楚:

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

你可能不需要派生状态

1. 从父节点设置一个键

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

2. 使用getDerivedStateFromProps / componentWillReceiveProps

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

通过使用getDerivedStateFromProps你可以重置状态的任何部分,但它似乎 这个时候有点bug (v16.7)!,

的用法见上面的链接

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

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

钩子的新方法是使用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检查对指定道具的更改,并在道具更改时执行更新状态的操作。

使用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 < / p >
// store the startTime prop in local state
const [startTime, setStartTime] = useState(props.startTime)
//
useEffect(() => {
if (props.startTime !== startTime) {
setStartTime(props.startTime);
}
}, [props.startTime]);

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

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

我使用功能组件和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>