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

Posted

技术标签:

【中文标题】React setState - 将数组添加到具有多个数组的嵌套对象【英文标题】:React setState - Add array to nested object with multiple arrays 【发布时间】:2018-04-25 20:32:53 【问题描述】:

我目前正在使用 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”,所以,我期待会为 period[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]
        ]
      
    )
  ]
;

非常感谢大家的帮助!!

【问题讨论】:

好吧,您正在使用相同的参考,您的defaultPromotion 也是您的this.state.promotion。尝试考虑您希望使用哪个状态引擎(例如 redux 或 mobx)并查看教程如何做到这一点 【参考方案1】:

所以,这里发生的情况是,您有一个数组,其中包含对同一对象的两个引用。

想象一下:

myArray[0] = reference to defaultPromotion
myArray[1] = reference to defaultPromotion

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

您在这里要做的不是将 defaultPromotion 对象添加到promotions 数组中,而是创建一个具有与该对象相同的道具的新对象并添加它。它看起来像这样(取决于您的 ES 版本等)

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

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

【讨论】:

您好帕特里克,感谢您的帮助!我已将您的答案与@bennygenel 的答案结合起来,在它工作正常的时期内。但是对于变体,它仍然是一样的:addVariant( periodKey ) this.setState((prevState) => const periods = prevState; periods[periodKey].variants.push( Object.assign(, api.promotions.defaultPromotion.periods[0].variants[0]) ); return periods ; ); 你能告诉我我做错了什么吗? 这可能是因为变量数组又是一个引用,你也需要在这里创建一个新数组,或者通过使用诸如 lodash lodash.com/docs/4.17.4#cloneDeep 之类的实用程序库,或者手动执行如下操作: [...periods[periodKey].variants, ...defaultPromotion.periods[0].variants[0] ]【参考方案2】:

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

【讨论】:

嗨 Merso,感谢您的回答,我快到了 :-D。我已经将我的代码重写为 redux,并且添加句点确实有效。添加变体也确实有效,但是当我在例如添加新变体时期间[2],然后我失去所有的期间。请参阅我的开始帖子中的更新 2。 嘿@Danny,这条线是错误的const periods = ...state 。请尝试将其替换为:const periods = state。这样你就可以从状态对象中拉出 prop periods 您好 Merso,非常感谢!我已经编辑了“PROMOTION_ADD_PERIOD”案例,所以它使用了扩展运算符,我认为那段代码现在应该是;-)。请参阅我的问题中的“更新 3”。但是“PROMOTION_ADD_PERIOD_VARIANT”中的问题仍然存在。添加新的期间变体时,我失去了所有的期间,新的变体被添加到 period[0]。 没问题@Danny!我们在这里为您提供帮助 :) 那么,PROMOTION_ADD_PERIOD_VARIANT 应该做什么?你的state 有一个periods 属性(数组),你想在period 的位置action.payload.period 处添加一个新的variant,对吗? 最短的答案是 PROMOTION ADD PERIOD VARIANT 应该复制 period[0].variants[0] 并将其添加到例如:state.periods[2].variants,其中 2 当然是periodKey 变量。这样, state.periods[2].variants 应该是一个数组,包含 2 个对象。就像我第二次更新中的状态一样,但没有删除我所有的时期。【参考方案3】:

第一个建议,如果您的状态中只有一个提升对象而不是数组,请失去提升级别。这将降低您的状态的复杂性。您可以使用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 ;
  );

【讨论】:

您好本尼,感谢您的评论。我的经期现在可以使用 setSate 正确处理,但是当使用您的代码添加新变体时,我的所有经期仍然获得新变体。你知道什么可能是错误的吗? 没有看到你的具体实现,我无话可说。确保不要为每个periodKey 运行addVariant。验证您从 api 接收的数据。使用控制台日志调试您的代码。 当我 console.log 在 addVariant 函数中的 periodKey 时,它确实显示了正确的 periodKey,所以它不会被多次调用。当它被多次调用时,我希望我会得到 0、1、2、3 等。 那么它应该可以工作。您能否通过确切的实施为您的问题添加更新。也许有人可能会添加我们都缺少的东西

以上是关于React setState - 将数组添加到具有多个数组的嵌套对象的主要内容,如果未能解决你的问题,请参考以下文章

React - 使用 setState 更新状态中的对象数组

React - 使用setState更新状态中的对象数组

React setState未在firestore中执行添加文档承诺解析

React:在 setState 中设置数组对象

在 react 中使用 setState 将预定义的对象推送到数组中

反应中嵌套数组中的setState