React:如何使用 setState 更新状态中的 state.item[1]?

React: how to update state.item[1] in state using setState?

我正在创建一个应用程序,用户可以在其中设计自己的表单。例如。指定字段的名称和应包含的其他列的详细信息。

该组件可作为 JSFiddle here

我的初始状态是这样的:

var DynamicForm = React.createClass({
  getInitialState: function() {
   var items = {};
   items[1] = { name: 'field 1', populate_at: 'web_start',
                same_as: 'customer_name',
                autocomplete_from: 'customer_name', title: '' };
   items[2] = { name: 'field 2', populate_at: 'web_end',
                same_as: 'user_name', 
                    autocomplete_from: 'user_name', title: '' };

     return { items };
   },

  render: function() {
     var _this = this;
     return (
       <div>
         { Object.keys(this.state.items).map(function (key) {
           var item = _this.state.items[key];
           return (
             <div>
               <PopulateAtCheckboxes this={this}
                 checked={item.populate_at} id={key} 
                   populate_at={data.populate_at} />
            </div>
            );
        }, this)}
        <button onClick={this.newFieldEntry}>Create a new field</button>
        <button onClick={this.saveAndContinue}>Save and Continue</button>
      </div>
    );
  }

我想在用户更改任何值时更新状态,但我很难定位正确的对象:

var PopulateAtCheckboxes = React.createClass({
  handleChange: function (e) {
     item = this.state.items[1];
     item.name = 'newName';
     items[1] = item;
     this.setState({items: items});
  },
  render: function() {
    var populateAtCheckbox = this.props.populate_at.map(function(value) {
      return (
        <label for={value}>
          <input type="radio" name={'populate_at'+this.props.id} value={value}
            onChange={this.handleChange} checked={this.props.checked == value}
            ref="populate-at"/>
          {value}
        </label>
      );
    }, this);
    return (
      <div className="populate-at-checkboxes">
        {populateAtCheckbox}
      </div>
    );
  }
});

我应该如何制作 this.setState 才能更新 items[1].name

首先获取您想要的项目,更改您想要的对象,然后将其设置回状态。 如果您使用键控对象,则通过仅在 getInitialState 中传递对象来使用状态的方式会更容易。

handleChange: function (e) {
   item = this.state.items[1];
   item.name = 'newName';
   items[1] = item;

   this.setState({items: items});
}

您可以使用 update immutability helper for this:

this.setState({
  items: update(this.state.items, {1: {name: {$set: 'updated field name'}}})
})

或者,如果您不关心使用 ===shouldComponentUpdate() 生命周期方法中检测此项的更改,您可以直接编辑状态并强制组件重新- render - 这实际上与@limelights 的回答相同,因为它将对象拉出状态并对其进行编辑。

this.state.items[1].name = 'updated field name'
this.forceUpdate()

Post-编辑添加:

查看 Simple Component Communication lesson from react-training 示例,了解如何将回调函数从状态保持父组件传递到需要触发状态更改的子组件。

使用 handleChange 上的事件找出已更改的元素,然后更新它。为此,您可能需要更改一些 属性 以识别并更新它。

见fiddlehttps://jsfiddle.net/69z2wepo/6164/

试试这个肯定有用,其他情况我试了但没用

import _ from 'lodash';

this.state.var_name  = _.assign(this.state.var_name, {
   obj_prop: 'changed_value',
});

走错了!

handleChange = (e) => {
    const { items } = this.state;
    items[1].name = e.target.value;

    // update state
    this.setState({
        items,
    });
};

正如许多更好的开发人员在评论中指出的那样:改变状态是错误的!

我花了一段时间才弄明白这一点。以上工作但它带走了 React 的力量。例如 componentDidUpdate 不会将其视为更新,因为它是直接修改的。

所以正确的方法应该是:

handleChange = (e) => {
    this.setState(prevState => ({
        items: {
            ...prevState.items,
            [prevState.items[1].name]: e.target.value,
        },
    }));
};

如何创建另一个组件(用于需要进入数组的对象)并将以下内容作为道具传递?

  1. 组件索引 - 索引将用于数组中的 create/update。
  2. set 函数 - 此函数根据组件索引将数据放入数组中。
<SubObjectForm setData={this.setSubObjectData}                                                            objectIndex={index}/>

这里可以根据使用这个SubObjectForm的位置传入{index}

setSubObjectData 可以是这样的。

 setSubObjectData: function(index, data){
      var arrayFromParentObject= <retrieve from props or state>;
      var objectInArray= arrayFromParentObject.array[index];
      arrayFromParentObject.array[index] = Object.assign(objectInArray, data);
 }

在 SubObjectForm 中,this.props.setData 可以在数据更改时调用,如下所示。

<input type="text" name="name" onChange={(e) => this.props.setData(this.props.objectIndex,{name: e.target.value})}/>

我会移动函数句柄更改并添加索引参数

handleChange: function (index) {
    var items = this.state.items;
    items[index].name = 'newName';
    this.setState({items: items});
},

到动态表单组件并将其作为道具传递给 PopulateAtCheckboxes 组件。当您遍历您的项目时,您可以包含一个额外的计数器(在下面的代码中称为索引)以传递给句柄更改,如下所示

{ Object.keys(this.state.items).map(function (key, index) {
var item = _this.state.items[key];
var boundHandleChange = _this.handleChange.bind(_this, index);
  return (
    <div>
        <PopulateAtCheckboxes this={this}
            checked={item.populate_at} id={key} 
            handleChange={boundHandleChange}
            populate_at={data.populate_at} />
    </div>
);
}, this)}

最后,您可以调用您的更改侦听器,如下所示

<input type="radio" name={'populate_at'+this.props.id} value={value} onChange={this.props.handleChange} checked={this.props.checked == value} ref="populate-at"/>

不要就地改变状态。它可能会导致意想不到的结果。我已经吸取教训了!始终使用 copy/clone,Object.assign() 是一个很好的选择:

item = Object.assign({}, this.state.items[1], {name: 'newName'});
items[1] = item;
this.setState({items: items});

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

如果您只需要更改 Array 的一部分, 您有一个状态设置为的 React 组件。

state = {items: [{name: 'red-one', value: 100}, {name: 'green-one', value: 999}]}

最好更新Array中的red-one如下:

const itemIndex = this.state.items.findIndex(i=> i.name === 'red-one');
const newItems = [
   this.state.items.slice(0, itemIndex),
   {name: 'red-one', value: 666},
   this.state.items.slice(itemIndex)
]

this.setState(newItems)

在React的状态下修改深度嵌套objects/variables,通常使用三种方法:vanilla JavaScript的Object.assignimmutability-helper and cloneDeep from Lodash.

还有许多其他不太受欢迎的第三方库可以实现此目的,但在本回答中,我将仅介绍这三个选项。此外,还存在一些额外的香草 JavaScript 方法,如数组展开(例如,请参阅@mpen 的回答),但它们不是很直观、易于使用且无法处理所有状态操作情况。

正如在对答案的最高投票评论中无数次指出的那样,其作者提出了状态的直接突变:只是不要那样做。这是一种无处不在的 React 反模式,将不可避免地导致不良后果。学习正确的方法。

让我们比较一下三种广泛使用的方法。

给定这个状态对象结构:

state = {
    outer: {
        inner: 'initial value'
    }
}

您可以使用以下方法更新最内层 inner 字段的值,而不影响其余状态。

1。 原版 JavaScript 的 Object.assign

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })

  React.useEffect(() => {
    console.log('Before the shallow copying:', outer.inner) // initial value
    const newOuter = Object.assign({}, outer, { inner: 'updated value' })
    console.log('After the shallow copy is taken, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>

<main id="react"></main>

请记住,Object.assign will not perform a deep cloning, since it only copies property values,这就是为什么它所做的被称为浅层复制(见评论)。

为此,我们应该只操作 primitive 类型 (outer.inner) 的属性,即字符串、数字、布尔值。

在此示例中,我们使用 Object.assign 创建一个新常量 (const newOuter...),它创建一个空对象 ({}),复制 outer对象 ({ inner: 'initial value' }) 复制到其中,然后复制一个不同的对象 { inner: 'updated value' } over 它。

这样,由于 inner 属性 被覆盖,最终新创建的 newOuter 常量将保持 { inner: 'updated value' } 的值。这个 newOuter 是一个 b运行d 新对象,它没有链接到状态中的对象,所以它可以根据需要改变,状态将保持不变,直到命令更新它时才会改变是 运行.

最后一部分是利用setOuter()setter将state中原来的outer替换为新创建的newOuter对象(只是值会变, 属性 名称 outer 不会)。

现在假设我们有一个像 state = { outer: { inner: { innerMost: 'initial value' } } } 这样更深的状态。我们可以尝试创建 newOuter 对象并用状态中的 outer 内容填充它,但是 Object.assign 将无法将 innerMost 的值复制到这个新的由于 innerMost 嵌套太深,已创建 newOuter 对象。

您仍然可以复制 inner,就像上面的示例一样,但由于它现在是一个对象并且 不是 原始对象,因此 reference 来自 newOuter.inner 将被复制到 outer.inner,这意味着我们最终将得到本地 newOuter 对象直接绑定到状态中的对象。

这意味着在这种情况下,本地创建的 newOuter.inner 的突变将直接影响 outer.inner 对象(在状态中),因为它们实际上变成了同一事物(在计算机的内存中) .

Object.assign 因此只有当你有一个相对简单的一级深度状态结构并且最里面的成员保存原始类型的值时才会工作。

如果您有更深层次的对象(第 2 级或更高级别),您应该更新这些对象,请不要使用 Object.assign。你冒着直接改变状态的风险。

2。 Lodash 的 cloneDeep

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })

  React.useEffect(() => {
    console.log('Before the deep cloning:', outer.inner) // initial value
    const newOuter = _.cloneDeep(outer) // cloneDeep() is coming from the Lodash lib
    newOuter.inner = 'updated value'
    console.log('After the deeply cloned object is modified, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

<main id="react"></main>

Lodash 的 cloneDeep 使用起来更简单。它执行 深度克隆 ,因此如果您的状态相当复杂且内部包含多级对象或数组,那么它是一个可靠的选择。只是 cloneDeep() 顶级状态 属性,以任何你喜欢的方式改变克隆的部分,然后 setOuter() 它回到状态。

3。 不变性助手

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })
  
  React.useEffect(() => {
    const update = immutabilityHelper
    console.log('Before the deep cloning and updating:', outer.inner) // initial value
    const newOuter = update(outer, { inner: { $set: 'updated value' } })
    console.log('After the cloning and updating, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://wzrd.in/standalone/immutability-helper@3.0.0"></script>

<main id="react"></main>

immutability-helper 将它提升到一个全新的水平,它的酷之处在于它不仅可以 $set 值来表示项目,而且可以 $push, $splice, $merge (等) 他们。这里有一个 list of commands 可用。

旁注

再次请记住,setOuter 仅修改状态对象(在这些示例中为 outer)的 第一级属性 ,而不是深层嵌套 (outer.inner)。如果它以不同的方式表现,这个问题就不会存在。

哪一个适合您的项目?

如果你不想或不能使用外部依赖,并且有一个简单的状态结构,坚持Object.assign.

如果你操纵一个巨大的and/or复杂状态,Lodash的cloneDeep是一个明智的选择。

如果您需要高级功能,即如果您的状态结构很复杂并且需要对其执行各种操作,请尝试immutability-helper,这是一个非常可用于状态操作的高级工具。

...或者,您真的需要这样做吗?

如果您在 React 的状态下持有复杂数据,也许现在是考虑其他处理方式的好时机。在 React 组件中设置一个复杂的状态对象并不是一个简单的操作,我强烈建议考虑不同的方法。

很可能你最好不要将复杂数据保存在 Redux 存储中,使用 reducer and/or sagas 将其设置在那里并使用选择器访问它。

无突变:

// given a state
state = {items: [{name: 'Fred', value: 1}, {name: 'Wilma', value: 2}]}

// This will work without mutation as it clones the modified item in the map:
this.state.items
   .map(item => item.name === 'Fred' ? {...item, ...{value: 3}} : item)

this.setState(newItems)

我遇到了同样的问题。这是一个有效的简单解决方案!

const newItems = [...this.state.items];
newItems[item] = value;
this.setState({ items:newItems });

由于此线程中存在大量错误信息,因此在没有帮助程序库的情况下,您可以通过以下方式完成此操作:

handleChange: function (e) {
    // 1. Make a shallow copy of the items
    let items = [...this.state.items];
    // 2. Make a shallow copy of the item you want to mutate
    let item = {...items[1]};
    // 3. Replace the property you're intested in
    item.name = 'newName';
    // 4. Put it back into our array. N.B. we *are* mutating the array here, but that's why we made a copy first
    items[1] = item;
    // 5. Set the state to our new copy
    this.setState({items});
},

如果需要,您可以合并第 2 步和第 3 步:

let item = {
    ...items[1],
    name: 'newName'
}

或者您可以在一行中完成所有操作:

this.setState(({items}) => ({
    items: [
        ...items.slice(0,1),
        {
            ...items[1],
            name: 'newName',
        },
        ...items.slice(2)
    ]
}));

注:我制作了items一个数组。 OP使用了一个对象。但是,概念是一样的。


您可以在 terminal/console:

中查看发生了什么
❯ node
> items = [{name:'foo'},{name:'bar'},{name:'baz'}]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> clone = [...items]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> item1 = {...clone[1]}
{ name: 'bar' }
> item1.name = 'bacon'
'bacon'
> clone[1] = item1
{ name: 'bacon' }
> clone
[ { name: 'foo' }, { name: 'bacon' }, { name: 'baz' } ]
> items
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ] // good! we didn't mutate `items`
> items === clone
false // these are different objects
> items[0] === clone[0]
true // we don't need to clone items 0 and 2 because we're not mutating them (efficiency gains!)
> items[1] === clone[1]
false // this guy we copied

真的很简单

首先从state拉取整个items对象,根据需要更新items对象的部分,然后通过setState将整个items对象放回state。

handleChange: function (e) {
  items = Object.assign(this.state.items); // Pull the entire items object out. Using object.assign is a good idea for objects.
  items[1].name = 'newName'; // update the items object as needed
  this.setState({ items }); // Put back in state
}

根据 setState 上的 React 文档,按照此处其他答案的建议使用 Object.assign 并不理想。由于 setState 的异步行为的性质,使用此技术的后续调用可能会覆盖先前的调用,从而导致不良结果。

相反,React 文档建议使用 setState 的更新程序形式,它在以前的状态上运行。请记住,在更新数组或对象时 你必须 return 一个新的数组或对象 因为 React 要求我们保持状态不变性。使用 ES6 语法的扩展运算符浅拷贝一个数组,在给定的数组索引处创建或更新一个对象的 属性 看起来像这样:

this.setState(prevState => {
    const newItems = [...prevState.items];
    newItems[index].name = newName;
    return {items: newItems};
})

由于上述选项中的 none 对我来说很理想,所以我最终使用了地图:

this.setState({items: this.state.items.map((item,idx)=> idx!==1 ?item :{...item,name:'new_name'}) })

或者如果您有一个动态生成的列表并且您不知道索引但只有键或 ID:

let ItemsCopy = []
let x = this.state.Items.map((entry) =>{

    if(entry.id == 'theIDYoureLookingFor')
    {
        entry.PropertyToChange = 'NewProperty'
    }

    ItemsCopy.push(entry)
})


this.setState({Items:ItemsCopy});

尝试使用代码:

this.state.items[1] = 'new value';
var cloneObj = Object.assign({}, this.state.items);

this.setState({items: cloneObj });

下面这段代码对我迟钝的大脑来说很容易。删除对象并替换为更新后的对象

    var udpateditem = this.state.items.find(function(item) { 
                   return item.name == "field_1" });
    udpateditem.name= "New updated name"                       
    this.setState(prevState => ({                                   
    items:prevState.dl_name_template.filter(function(item) { 
                                    return item.name !== "field_1"}).concat(udpateditem)
    }));
 handleChanges = (value, key) => {
     // clone the current State object
    let cloneObject = _.extend({}, this.state.currentAttribute);
    // key as user.name and value= "ABC" then current attributes have current properties as we changes
    currentAttribute[key] = value;
    // then set the state "currentAttribute" is key and "cloneObject" is changed object.  
    this.setState({currentAttribute: cloneObject});

并从文本框更改添加 onChange 事件

onChange = {
   (event) => {                                                
      this.handleChanges(event.target.value, "title");
   }
}

发现这非常困难,none 的 ES6 传播魔法似乎按预期工作。 正在使用这样的结构来获取用于布局目的的渲染元素属性。

发现使用 immutability-helper 中的 update 方法是这个简化示例中最直接的方法:

constructor(props) {
    super(props)
    this.state = { values: [] }
    this.updateContainerState = this.updateContainerState.bind(this)
  }

updateContainerState(index, value) {
    this.setState((state) => update(state, { values: { [index]: { $set: value } } }))
  }

改编自

待更新的数组成员是一个更复杂的嵌套object根据复杂性使用

肯定有更好的方法来处理布局参数,但这是关于如何处理数组的。每个 child 元素的相关值也可以在它们之外计算,但我发现向下传递 containerState 更方便,因此它们 child 可以随意获取属性并更新 parent 给定索引处的状态数组。

import React from 'react'
import update from 'immutability-helper'
import { ContainerElement } from './container.component.style.js'
import ChildComponent from './child-component'
export default class ContainerComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { values: [] }
    this.updateContainerState = this.updateContainerState.bind(this)
  }

  updateContainerState(index, value) {
    this.setState((state) => update(state, { values: { [index]: { $set: value } } }))
  }

  // ...

  render() {
    let index = 0
    return (
      <ContainerElement>
      <ChildComponent
        index={index++}
        containerState={this.state}
        updateContainerState={this.updateContainerState}
      />
      <ChildComponent
        index={index++}
        containerState={this.state}
        updateContainerState={this.updateContainerState}
      />
      </ContainerElement>
    )
  }
}
this.setState({
      items: this.state.items.map((item,index) => {
        if (index === 1) {
          item.name = 'newName';
        }
        return item;
      })
    });

在一行中使用带有箭头函数的数组映射

this.setState({
    items: this.state.items.map((item, index) =>
      index === 1 ? { ...item, name: 'newName' } : item,
   )
})

@JonnyBuchanan 的回答非常有效,但仅适用于数组状态变量。如果状态变量只是一个字典,请遵循:

inputChange = input => e => {
    this.setState({
        item: update(this.state.item, {[input]: {$set: e.target.value}})
    })
}

您可以用字典的字段名替换 [input],用它的值替换 e.target.value。此代码对我的表单的输入更改事件执行更新作业。

有时在 React 中,改变克隆的数组会影响原始数组,这种方法永远不会导致改变:

    const myNewArray = Object.assign([...myArray], {
        [index]: myNewItem
    });
    setState({ myArray: myNewArray });

或者,如果您只想更新某项的 属性:

    const myNewArray = Object.assign([...myArray], {
        [index]: {
            ...myArray[index],
            prop: myNewValue
        }
    });
    setState({ myArray: myNewArray });