为什么我的道具在我更新状态之前更新?
Why is my props updating before I update the state?
我正在尝试更改 GrandChild class 和 Bootstrap Table 内的输入 Parentclass*。用户会更改 **GrandChild class 中的输入然后保存它,因此更改会在 Bootstrap Table 中看到 Parentclass;但是,在我调用 .onChange(这是我的保存)之前,我看到了我的道具发生变化的奇怪行为。我相信这会导致我的输入无法正确保存或设置状态。
数据向下传递层次结构:GrandParent => Parent => Child => GrandChild
它发生在 Child class 的 handleSave() 函数中:
export class Child extends React.Component {
constructor(props){
this.state = {
data:this.props.data
}
}
handleChange = (name, value) => {
this.setState((prevState) => {
let newState = {...prevState};
newState.data.dataList[0][name] = value; // data
return newState;
});
};
handleSave(){
let dataList = this.state.data.dataList.slice();
console.log("dataList state-dataList:", dataList);
console.log("dataList before onChange 2:", this.props.data.dataList); //Same as dataList or this.state.data.dataList
this.props.onChange("dataList", dataList);
console.log("dataList onChange 3:", this.props.data.dataList); //Same as dataList or this.state.data.dataList
}
render() {
return (
<div>
<GrandChild data={this.state.data} onChange={this.handleChange} />
</div>
)
}
Child class 的 this.props.onChange 被发送回 Parentclass:
export class Parent extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
columns = [
{dataField: '..', text: '...' },
{dataField: '..', text: '...' },
{dataField: '..', text: '...' },
{dataField: '..', text: '...'}];
handleChange = (name, value) => {
this.props.onChange(name, value);
};
render() {
return (
<div>
<BootstrapTable
hover
condensed={true}
bootstrap4={true}
keyField={'id'}
data={this.props.data.dataList}
columns={this.columns}
/>
<Child data={this.props.data} onChange={this.handleChange} />
</div>
);
}
}
然后 Parent class 的 this.props.onChange* 被发送到 GrandParent Class:
export class GrandParent extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {...this.props.location.state.data}
};
this.handleChange = this.handleChange.bind(this);
}
handleChange = (name, value) => {
this.setState((prevState) => {
let newState = {};
let data = Object.assign({}, prevState.data);
data[name] = value;
newState.data = data;
return newState;
});
};
render() {
return (
<div>
<Form>
<Parent data={this.state.data} onChange={this.handleChange} />
</Form>
</div>
)
}
这是GrandChild的class:
export class GrandChild extends React.Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange = (event) => {
const target = event.target;
const value = target.type === 'checkbox' ?
target.checked :
target.value;
const name = target.name;
this.props.onChange(name, value);
};
render() {
return (
<div>
<Form.Row>
<Form.Group as={Col}>
<Form.Label>Label Name</Form.Label>
<Form.Control name="labelName" value={this.props.data.[0]labelName || ""} //ignore the index for now
onChange={this.handleInputChange}/>
</Form.Group>
</Form.Row>
</div>
)
}
}
我预计数据列表的 console.logs() 会有所不同;但是,它们甚至在运行 this.props.onChange("dataList", dataList);
之前给出了相同的确切 object
第三个数据列表 console.log 可能与状态数据列表相同,因为 setState 是异步的。
看起来主要问题是您在 Child
:
中改变 state/props
handleChange = (name, value) => {
this.setState((prevState) => {
// {...prevState} makes a shallow copy of `prevState`
let newState = {...prevState};
// Next you're modifying data deep in `newState`,
// so it's mutating data in the `dataList` array,
// which updates the source of truth for both props and state.
newState.data.dataList[0][name] = value;
return newState;
});
};
一种方法(避免突变)是这样的:
handleChange = (name, value) => {
this.setState(prevState => ({
data: {
...prevState.data,
dataList: [
{
...prevState.data.dataList[0],
[name]: value
},
...prevState.data.dataList.slice(1)
]
}
}));
};
如果这比您想要的更冗长,您可以使用像 immutable-js
.
这样的库
另一个可能导致错误的问题是将道具复制到状态中。这篇文章解释了为什么这样不好:https://overreacted.io/writing-resilient-components/#dont-stop-the-data-flow-in-rendering
基本上:如果您在状态中设置 props,然后更新状态并将 props 传递给 child,您传递的数据将会过时。它看起来不像你在这里做的那样,但很容易错过。避免这种情况的一个简单方法是命名您计划在状态 initialProp
中设置的任何道具如果您的道具被命名为 initialData
,很明显从树中的那个点开始您应该依赖该值在状态而不是道具中。
另外,Grandparent
中的handleChange
可以写得更简单:
handleChange = (name, value) => {
this.setState(prevState => ({
data: {
...prevState.data,
[name]: value
}
}))
};
我正在尝试更改 GrandChild class 和 Bootstrap Table 内的输入 Parentclass*。用户会更改 **GrandChild class 中的输入然后保存它,因此更改会在 Bootstrap Table 中看到 Parentclass;但是,在我调用 .onChange(这是我的保存)之前,我看到了我的道具发生变化的奇怪行为。我相信这会导致我的输入无法正确保存或设置状态。
数据向下传递层次结构:GrandParent => Parent => Child => GrandChild
它发生在 Child class 的 handleSave() 函数中:
export class Child extends React.Component {
constructor(props){
this.state = {
data:this.props.data
}
}
handleChange = (name, value) => {
this.setState((prevState) => {
let newState = {...prevState};
newState.data.dataList[0][name] = value; // data
return newState;
});
};
handleSave(){
let dataList = this.state.data.dataList.slice();
console.log("dataList state-dataList:", dataList);
console.log("dataList before onChange 2:", this.props.data.dataList); //Same as dataList or this.state.data.dataList
this.props.onChange("dataList", dataList);
console.log("dataList onChange 3:", this.props.data.dataList); //Same as dataList or this.state.data.dataList
}
render() {
return (
<div>
<GrandChild data={this.state.data} onChange={this.handleChange} />
</div>
)
}
Child class 的 this.props.onChange 被发送回 Parentclass:
export class Parent extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
columns = [
{dataField: '..', text: '...' },
{dataField: '..', text: '...' },
{dataField: '..', text: '...' },
{dataField: '..', text: '...'}];
handleChange = (name, value) => {
this.props.onChange(name, value);
};
render() {
return (
<div>
<BootstrapTable
hover
condensed={true}
bootstrap4={true}
keyField={'id'}
data={this.props.data.dataList}
columns={this.columns}
/>
<Child data={this.props.data} onChange={this.handleChange} />
</div>
);
}
}
然后 Parent class 的 this.props.onChange* 被发送到 GrandParent Class:
export class GrandParent extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {...this.props.location.state.data}
};
this.handleChange = this.handleChange.bind(this);
}
handleChange = (name, value) => {
this.setState((prevState) => {
let newState = {};
let data = Object.assign({}, prevState.data);
data[name] = value;
newState.data = data;
return newState;
});
};
render() {
return (
<div>
<Form>
<Parent data={this.state.data} onChange={this.handleChange} />
</Form>
</div>
)
}
这是GrandChild的class:
export class GrandChild extends React.Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange = (event) => {
const target = event.target;
const value = target.type === 'checkbox' ?
target.checked :
target.value;
const name = target.name;
this.props.onChange(name, value);
};
render() {
return (
<div>
<Form.Row>
<Form.Group as={Col}>
<Form.Label>Label Name</Form.Label>
<Form.Control name="labelName" value={this.props.data.[0]labelName || ""} //ignore the index for now
onChange={this.handleInputChange}/>
</Form.Group>
</Form.Row>
</div>
)
}
}
我预计数据列表的 console.logs() 会有所不同;但是,它们甚至在运行 this.props.onChange("dataList", dataList);
之前给出了相同的确切 object第三个数据列表 console.log 可能与状态数据列表相同,因为 setState 是异步的。
看起来主要问题是您在 Child
:
handleChange = (name, value) => {
this.setState((prevState) => {
// {...prevState} makes a shallow copy of `prevState`
let newState = {...prevState};
// Next you're modifying data deep in `newState`,
// so it's mutating data in the `dataList` array,
// which updates the source of truth for both props and state.
newState.data.dataList[0][name] = value;
return newState;
});
};
一种方法(避免突变)是这样的:
handleChange = (name, value) => {
this.setState(prevState => ({
data: {
...prevState.data,
dataList: [
{
...prevState.data.dataList[0],
[name]: value
},
...prevState.data.dataList.slice(1)
]
}
}));
};
如果这比您想要的更冗长,您可以使用像 immutable-js
.
另一个可能导致错误的问题是将道具复制到状态中。这篇文章解释了为什么这样不好:https://overreacted.io/writing-resilient-components/#dont-stop-the-data-flow-in-rendering
基本上:如果您在状态中设置 props,然后更新状态并将 props 传递给 child,您传递的数据将会过时。它看起来不像你在这里做的那样,但很容易错过。避免这种情况的一个简单方法是命名您计划在状态 initialProp
中设置的任何道具如果您的道具被命名为 initialData
,很明显从树中的那个点开始您应该依赖该值在状态而不是道具中。
另外,Grandparent
中的handleChange
可以写得更简单:
handleChange = (name, value) => {
this.setState(prevState => ({
data: {
...prevState.data,
[name]: value
}
}))
};