React setState - 将数组添加到具有多个数组的嵌套对象
React setState - Add array to nested object with multiple arrays
我目前正在使用 React 开发一个新应用程序。这是我第一次在 React 中创建东西。该应用程序将显示我们自己的促销活动。
我的初始状态如下:
{
"promotion": {
"name": "",
"campaign": "",
"url": "https://",
"position": 0,
"periods": [
{
"startDateTimeStamp": 1510558814960,
"endDateTimeStamp": 1510558814960,
"variants": [
{
"title": "",
"text": "",
"image": ""
}
]
}
]
}
}
这是根据我的 defaultPromotion 常量创建的。这个常量存储在一个单独的文件中,我称之为 api.js
export const defaultPromotion = {
name: '',
campaign: '',
url: 'https://',
position: 0,
periods: [
{
startDateTimeStamp: Date.now(),
endDateTimeStamp: Date.now(),
variants: [
{
title: '',
text: '',
image: '',
},
]
},
]
}
在我的 createPromotion 组件中,它的创建方式如下
let promotionState = api.promotions.defaultPromotion;
this.state = {
promotion: promotionState
};
我可以添加一个新的句点:
addPromotion() {
let promotion = this.state.promotion;
promotion.periods.push( api.promotions.defaultPromotion.periods[0] );
this.forceUpdate();
}
之后,按预期添加了新的句点。非常欢迎使用 setState() 执行此操作的建议!所以,我现在的新状态是:
{
"promotion": {
"name": "",
"campaign": "",
"url": "https://",
"position": 0,
"periods": [
{
"startDateTimeStamp": 1510559984421,
"endDateTimeStamp": 1510559984421,
"variants": [
{
"title": "",
"text": "",
"image": ""
}
]
},
{
"startDateTimeStamp": 1510559984421,
"endDateTimeStamp": 1510559984421,
"variants": [
{
"title": "",
"text": "",
"image": ""
}
]
}
]
}
}
现在,我想为这个促销期添加一个新的变体,这是我现在卡了2天的地方。
我要添加一个新的句点如下:
addVariant( periodKey ) {
const promotion = this.state.promotion;
promotion.periods[periodKey].variants.push(api.promotions.defaultPromotion.periods[0].variants[0]);
this.setState({ promotion: promotion });
}
periodKey 在这里为“1”,因此,我希望为周期 [1] 添加一个新变体,但是,它已添加到两个周期。现状态如下:
{
"promotion": {
"name": "",
"campaign": "",
"url": "https://",
"position": 0,
"periods": [
{
"startDateTimeStamp": 1510559984421,
"endDateTimeStamp": 1510559984421,
"variants": [
{
"title": "",
"text": "",
"image": ""
},
{
"title": "",
"text": "",
"image": ""
}
]
},
{
"startDateTimeStamp": 1510559984421,
"endDateTimeStamp": 1510559984421,
"variants": [
{
"title": "",
"text": "",
"image": ""
},
{
"title": "",
"text": "",
"image": ""
}
]
}
]
}
}
有人可以向我解释为什么会发生这种情况以及如何以正确的方式添加新变体吗?
非常非常感谢!
更新 1
根据 bennygenel 和 Patrick Hübl-Neschkudla 的回答,我现在的实现如下:
设置初始状态:
constructor(props) {
super(props);
let promotionState = api.promotions.defaultPromotion;
this.state = { ...promotionState };
}
方法:
addVariant( periodKey ) {
this.setState((prevState) => {
const { periods } = prevState;
periods[periodKey].variants.push(
Object.assign({}, { ...periods[periodKey].variants, api.promotions.defaultPromotion.periods[0].variants[0]})
);
return { periods };
});
}
但这仍然是历代新变种。我也试过 Benny 的确切代码,但结果相同。该方法称为
this.props.addVariant( this.props.periodKey );
即使我称它为:
this.props.addVariant(2);
同样的行为正在发生。
更新 2
我现在已经将所有内容重写为 redux,这样我就可以轻松地访问每个组件中的促销信息,而不是通过某些组件传递它们。根据@mersocarlin 的回答,我现在有以下 reducer 案例:
添加句点
case PROMOTION_ADD_PERIOD:
const { periods } = { ...state };
periods.push(api.promotions.defaultPromotion.periods[0]);
state = {
...state,
periods: periods
};
break;
添加期间变量
case PROMOTION_ADD_PERIOD_VARIANT :
state = {
...state,
periods: [
...state.periods[action.payload.period],
{
variants: [
...state.periods[action.payload.period].variants,
api.promotions.defaultPromotion.periods[0].variants[0]
]
}
]
};
break;
以下案例:
添加一个新的变体,作品,状态:
{
"name": "",
"campaign": "",
"url": "https://",
"position": 0,
"periods": [
{
"startDateTimeStamp": 1510599968588,
"endDateTimeStamp": 1510599968588,
"variants": [
{
"title": "",
"text": "",
"image": ""
}
]
},
{
"startDateTimeStamp": 1510599968594,
"endDateTimeStamp": 1510599968594,
"variants": [
{
"title": "",
"text": "",
"image": ""
}
]
}
]
}
在那之后,添加一个新的变体,有点效果,好吧,变体被添加了,但我失去了我的第二节课。状态:
{
"name": "",
"campaign": "",
"url": "https://",
"position": 0,
"periods": [
{
"variants": [
{
"title": "",
"text": "",
"image": ""
},
{
"title": "",
"text": "",
"image": ""
}
]
}
]
}
我认为这是一件我没有看到的小事。有人知道 "PROMOTION_ADD_PERIOD_VARIANT" 案例的解决方案吗?
更新 3
将 "PROMOTION_ADD_PERIOD" 大小写更改如下:
case PROMOTION_ADD_PERIOD:
state = {
...state,
periods: [
...state.periods,
initialState.periods[0]
]
};
break;
更新 4
最后找到了解决方案。请参阅下面 PROMOTION_ADD_PERIOD_VARIANT 的最终代码:
state = {
...state,
periods: [
...state.periods.map((item, index) => {
if ( index !== action.payload.period ) {
return item;
}
return {
...item,
variants: [
...item.variants,
initialState.periods[0].variants[0]
]
}
})
]
};
非常感谢大家的帮助!!
所以,这里发生的事情是您有一个数组,其中包含对同一对象的两个引用。
想象一下:
myArray[0] = reference to defaultPromotion
myArray[1] = reference to defaultPromotion
这实际上是为什么不变性概念在过去几年受到如此多关注的一个很好的例子:)
您在这里要做的不是将 defaultPromotion 对象添加到促销数组,而是创建一个与此对象具有相同道具的新对象并添加它。它看起来像这样(取决于你的 ES 版本等)
promotion.periods.push(
Object.assign({}, api.promotions.defaultPromotion.periods[0])
);
这样,您将创建一个新对象并将其传递给数组,而不是对已经存在的对象的引用。
而是 destruct 你的状态对象,避免直接改变它。这也恰好是一个糟糕的模式。
每当您需要向数组添加新项目时:
const state = {
arrayProp: [{ prop1: 'prop1', prop2: 'prop2' }]
}
const newItem = {
prop1: 'value1',
prop2: 'value2',
}
const newState = {
...state,
arrayProp: [
...state.arrayProp,
newItem,
]
}
console.log('newState', newState)
同样适用于您所在州的嵌套属性:
Redux also uses this very same approach
const state = {
objectProp: {
arrayPropWithinArray: [
{ id: '0', otherProp: 123, yetAnotherProp: 'test' },
{ id: '1', otherProp: 0, yetAnotherProp: '' }
]
}
}
const { objectProp } = state
const index = objectProp.arrayPropWithinArray.findIndex(obj => obj.id === '1')
const newSubItem = {
otherProp: 1,
yetAnotherProp: '2',
}
const newState = {
...state,
objectProp: {
...objectProp,
arrayPropWithinArray: [
...objectProp.arrayPropWithinArray.slice(0, index),
{
...objectProp.arrayPropWithinArray[index],
...newSubItem,
},
...objectProp.arrayPropWithinArray.slice(index + 1),
]
}
}
console.log('newState', newState)
您的具体情况(如您的评论所述)
const periodKey = '2' // your periodKey var. Get it from the right place, it can be your action for example
const index = state.periods.findIndex(period => period.id === periodKey) // find which index has to be updated
state = {
...state, // propagates current state
periods: [
...state.periods.slice(0, index), // propagates everything before index
{
...state.periods[index],
variants: [
...state.periods[index].variants,
api.promotions.defaultPromotion.periods[0].variants[0],
],
},
...state.periods.slice(0, index + 1) // propagates everything after index
]
}
第一个建议,如果你打算在你的状态中只有一个提升对象而不是一个数组,那么失去提升级别。这将降低您所在州的复杂性。您可以使用 spread syntax 轻松设置您的初始状态。
例子
let promotionState = api.promotions.defaultPromotion;
this.state = { ...promotionState };
以上代码最终会创建如下状态;
{
"name": "",
"campaign": "",
"url": "https://",
"position": 0,
"periods": [{
"startDateTimeStamp": 1510559984421,
"endDateTimeStamp": 1510559984421,
"variants": [{
"title": "",
"text": "",
"image": ""
}]
}, {
"startDateTimeStamp": 1510559984421,
"endDateTimeStamp": 1510559984421,
"variants": [{
"title": "",
"text": "",
"image": ""
}]
}]
}
我可以提出的另一个建议是使用函数 setState
来减少变异的可能性。
例子
addPromotion() {
this.setState((prevState) => {
const { periods } = prevState;
periods.push(api.promotions.defaultPromotion.periods[0]);
return { periods };
});
}
addVariant( periodKey ) {
this.setState((prevState) => {
const { periods } = prevState;
periods[periodKey].variants.push(api.promotions.defaultPromotion.periods[0].variants[0]);
return { periods };
});
}
我目前正在使用 React 开发一个新应用程序。这是我第一次在 React 中创建东西。该应用程序将显示我们自己的促销活动。
我的初始状态如下:
{
"promotion": {
"name": "",
"campaign": "",
"url": "https://",
"position": 0,
"periods": [
{
"startDateTimeStamp": 1510558814960,
"endDateTimeStamp": 1510558814960,
"variants": [
{
"title": "",
"text": "",
"image": ""
}
]
}
]
}
}
这是根据我的 defaultPromotion 常量创建的。这个常量存储在一个单独的文件中,我称之为 api.js
export const defaultPromotion = {
name: '',
campaign: '',
url: 'https://',
position: 0,
periods: [
{
startDateTimeStamp: Date.now(),
endDateTimeStamp: Date.now(),
variants: [
{
title: '',
text: '',
image: '',
},
]
},
]
}
在我的 createPromotion 组件中,它的创建方式如下
let promotionState = api.promotions.defaultPromotion;
this.state = {
promotion: promotionState
};
我可以添加一个新的句点:
addPromotion() {
let promotion = this.state.promotion;
promotion.periods.push( api.promotions.defaultPromotion.periods[0] );
this.forceUpdate();
}
之后,按预期添加了新的句点。非常欢迎使用 setState() 执行此操作的建议!所以,我现在的新状态是:
{
"promotion": {
"name": "",
"campaign": "",
"url": "https://",
"position": 0,
"periods": [
{
"startDateTimeStamp": 1510559984421,
"endDateTimeStamp": 1510559984421,
"variants": [
{
"title": "",
"text": "",
"image": ""
}
]
},
{
"startDateTimeStamp": 1510559984421,
"endDateTimeStamp": 1510559984421,
"variants": [
{
"title": "",
"text": "",
"image": ""
}
]
}
]
}
}
现在,我想为这个促销期添加一个新的变体,这是我现在卡了2天的地方。
我要添加一个新的句点如下:
addVariant( periodKey ) {
const promotion = this.state.promotion;
promotion.periods[periodKey].variants.push(api.promotions.defaultPromotion.periods[0].variants[0]);
this.setState({ promotion: promotion });
}
periodKey 在这里为“1”,因此,我希望为周期 [1] 添加一个新变体,但是,它已添加到两个周期。现状态如下:
{
"promotion": {
"name": "",
"campaign": "",
"url": "https://",
"position": 0,
"periods": [
{
"startDateTimeStamp": 1510559984421,
"endDateTimeStamp": 1510559984421,
"variants": [
{
"title": "",
"text": "",
"image": ""
},
{
"title": "",
"text": "",
"image": ""
}
]
},
{
"startDateTimeStamp": 1510559984421,
"endDateTimeStamp": 1510559984421,
"variants": [
{
"title": "",
"text": "",
"image": ""
},
{
"title": "",
"text": "",
"image": ""
}
]
}
]
}
}
有人可以向我解释为什么会发生这种情况以及如何以正确的方式添加新变体吗?
非常非常感谢!
更新 1
根据 bennygenel 和 Patrick Hübl-Neschkudla 的回答,我现在的实现如下:
设置初始状态:
constructor(props) {
super(props);
let promotionState = api.promotions.defaultPromotion;
this.state = { ...promotionState };
}
方法:
addVariant( periodKey ) {
this.setState((prevState) => {
const { periods } = prevState;
periods[periodKey].variants.push(
Object.assign({}, { ...periods[periodKey].variants, api.promotions.defaultPromotion.periods[0].variants[0]})
);
return { periods };
});
}
但这仍然是历代新变种。我也试过 Benny 的确切代码,但结果相同。该方法称为
this.props.addVariant( this.props.periodKey );
即使我称它为:
this.props.addVariant(2);
同样的行为正在发生。
更新 2
我现在已经将所有内容重写为 redux,这样我就可以轻松地访问每个组件中的促销信息,而不是通过某些组件传递它们。根据@mersocarlin 的回答,我现在有以下 reducer 案例:
添加句点
case PROMOTION_ADD_PERIOD:
const { periods } = { ...state };
periods.push(api.promotions.defaultPromotion.periods[0]);
state = {
...state,
periods: periods
};
break;
添加期间变量
case PROMOTION_ADD_PERIOD_VARIANT :
state = {
...state,
periods: [
...state.periods[action.payload.period],
{
variants: [
...state.periods[action.payload.period].variants,
api.promotions.defaultPromotion.periods[0].variants[0]
]
}
]
};
break;
以下案例: 添加一个新的变体,作品,状态:
{
"name": "",
"campaign": "",
"url": "https://",
"position": 0,
"periods": [
{
"startDateTimeStamp": 1510599968588,
"endDateTimeStamp": 1510599968588,
"variants": [
{
"title": "",
"text": "",
"image": ""
}
]
},
{
"startDateTimeStamp": 1510599968594,
"endDateTimeStamp": 1510599968594,
"variants": [
{
"title": "",
"text": "",
"image": ""
}
]
}
]
}
在那之后,添加一个新的变体,有点效果,好吧,变体被添加了,但我失去了我的第二节课。状态:
{
"name": "",
"campaign": "",
"url": "https://",
"position": 0,
"periods": [
{
"variants": [
{
"title": "",
"text": "",
"image": ""
},
{
"title": "",
"text": "",
"image": ""
}
]
}
]
}
我认为这是一件我没有看到的小事。有人知道 "PROMOTION_ADD_PERIOD_VARIANT" 案例的解决方案吗?
更新 3 将 "PROMOTION_ADD_PERIOD" 大小写更改如下:
case PROMOTION_ADD_PERIOD:
state = {
...state,
periods: [
...state.periods,
initialState.periods[0]
]
};
break;
更新 4 最后找到了解决方案。请参阅下面 PROMOTION_ADD_PERIOD_VARIANT 的最终代码:
state = {
...state,
periods: [
...state.periods.map((item, index) => {
if ( index !== action.payload.period ) {
return item;
}
return {
...item,
variants: [
...item.variants,
initialState.periods[0].variants[0]
]
}
})
]
};
非常感谢大家的帮助!!
所以,这里发生的事情是您有一个数组,其中包含对同一对象的两个引用。
想象一下:
myArray[0] = reference to defaultPromotion
myArray[1] = reference to defaultPromotion
这实际上是为什么不变性概念在过去几年受到如此多关注的一个很好的例子:)
您在这里要做的不是将 defaultPromotion 对象添加到促销数组,而是创建一个与此对象具有相同道具的新对象并添加它。它看起来像这样(取决于你的 ES 版本等)
promotion.periods.push(
Object.assign({}, api.promotions.defaultPromotion.periods[0])
);
这样,您将创建一个新对象并将其传递给数组,而不是对已经存在的对象的引用。
而是 destruct 你的状态对象,避免直接改变它。这也恰好是一个糟糕的模式。
每当您需要向数组添加新项目时:
const state = {
arrayProp: [{ prop1: 'prop1', prop2: 'prop2' }]
}
const newItem = {
prop1: 'value1',
prop2: 'value2',
}
const newState = {
...state,
arrayProp: [
...state.arrayProp,
newItem,
]
}
console.log('newState', newState)
同样适用于您所在州的嵌套属性: Redux also uses this very same approach
const state = {
objectProp: {
arrayPropWithinArray: [
{ id: '0', otherProp: 123, yetAnotherProp: 'test' },
{ id: '1', otherProp: 0, yetAnotherProp: '' }
]
}
}
const { objectProp } = state
const index = objectProp.arrayPropWithinArray.findIndex(obj => obj.id === '1')
const newSubItem = {
otherProp: 1,
yetAnotherProp: '2',
}
const newState = {
...state,
objectProp: {
...objectProp,
arrayPropWithinArray: [
...objectProp.arrayPropWithinArray.slice(0, index),
{
...objectProp.arrayPropWithinArray[index],
...newSubItem,
},
...objectProp.arrayPropWithinArray.slice(index + 1),
]
}
}
console.log('newState', newState)
您的具体情况(如您的评论所述)
const periodKey = '2' // your periodKey var. Get it from the right place, it can be your action for example
const index = state.periods.findIndex(period => period.id === periodKey) // find which index has to be updated
state = {
...state, // propagates current state
periods: [
...state.periods.slice(0, index), // propagates everything before index
{
...state.periods[index],
variants: [
...state.periods[index].variants,
api.promotions.defaultPromotion.periods[0].variants[0],
],
},
...state.periods.slice(0, index + 1) // propagates everything after index
]
}
第一个建议,如果你打算在你的状态中只有一个提升对象而不是一个数组,那么失去提升级别。这将降低您所在州的复杂性。您可以使用 spread syntax 轻松设置您的初始状态。
例子
let promotionState = api.promotions.defaultPromotion;
this.state = { ...promotionState };
以上代码最终会创建如下状态;
{
"name": "",
"campaign": "",
"url": "https://",
"position": 0,
"periods": [{
"startDateTimeStamp": 1510559984421,
"endDateTimeStamp": 1510559984421,
"variants": [{
"title": "",
"text": "",
"image": ""
}]
}, {
"startDateTimeStamp": 1510559984421,
"endDateTimeStamp": 1510559984421,
"variants": [{
"title": "",
"text": "",
"image": ""
}]
}]
}
我可以提出的另一个建议是使用函数 setState
来减少变异的可能性。
例子
addPromotion() {
this.setState((prevState) => {
const { periods } = prevState;
periods.push(api.promotions.defaultPromotion.periods[0]);
return { periods };
});
}
addVariant( periodKey ) {
this.setState((prevState) => {
const { periods } = prevState;
periods[periodKey].variants.push(api.promotions.defaultPromotion.periods[0].variants[0]);
return { periods };
});
}