React setState - 将数组添加到具有多个数组的嵌套对象

时间:2017-11-13 08:08:45

标签: arrays reactjs object immutability setstate

我目前正在研究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和PatrickHü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的答案,我现在有以下减速器情况:

添加期间

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

非常感谢你的帮助!!

3 个答案:

答案 0 :(得分:1)

所以,这里发生的是你有一个数组,其中有两个对同一个对象的引用。

想象一下:

promotion.periods.push(
  Object.assign({}, api.promotions.defaultPromotion.periods[0])
);

这实际上是为什么不变性概念在过去几年中备受关注的一个很好的例子:)

您要在此处执行的操作是,不是将defaultPromotion对象添加到促销数组,而是使用与此对象相同的道具创建一个新对象并添加它。它看起来像这样(取决于你的ES版本等)。

nightwatch.js

这样,您将创建一个新对象并将其传递给数组,而不是对现有对象的引用。

答案 1 :(得分:1)

而是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
  ]
}

答案 2 :(得分:1)

首先建议,如果您的州中只有一个促销对象而不是数组,则会失去促销级别。这将降低您所在州的复杂程度。您可以使用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 };
  });
}