如何更新状态。项目[1]状态使用setState?

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

该组件可作为JSFiddle 在这里

初始状态是这样的

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 ?

595919 次浏览

首先获取你想要的项目,在该对象上更改你想要的内容并将其设置回状态。 如果你使用键控对象,你只通过在getInitialState中传递一个对象来使用状态的方式会更容易

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


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

你可以使用update不可变helper:

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

或者,如果你不关心是否能够在shouldComponentUpdate()生命周期方法中使用===检测该项的更改,你可以直接编辑状态并强制组件重新呈现——这实际上与@limelights的答案相同,因为它正在将对象拉出状态并编辑它。

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

后编之外:

查看react-training中的简单组件通信课程,了解如何将一个回调函数从持有状态的父组件传递给需要触发状态更改的子组件。

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

参见小提琴https://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. 组件索引-索引将用于在数组中创建/更新。
  2. set function——这个函数根据组件索引将数据放入数组中。
<SubObjectForm setData={this.setSubObjectData}                                                            objectIndex={index}/>

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

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})}/>

我将移动函数句柄change并添加一个索引参数

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

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

{ 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"/>

不要原地改变状态。它会导致意想不到的结果。我已经吸取教训了!总是使用复制/克隆,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的状态下修改嵌套很深的对象/变量,通常使用三种方法:JavaScript的Object.assignimmutability-helpercloneDeep 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 不会进行深度克隆吗,因为它只复制属性值,这就是为什么它所做的被称为浅拷贝(见注释)。

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

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

这样,最后新创建的newOuter常量将保存一个{ inner: 'updated value' }的值,因为inner属性被覆盖了。这个newOuter是一个全新的对象,它没有链接到状态中的对象,所以它可以根据需要进行突变,状态将保持不变,直到运行更新它的命令。

最后一部分是使用setOuter() setter将状态中的原始outer替换为新创建的newOuter对象(只有值会改变,属性名outer不会改变)。

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

你仍然可以像上面的例子一样复制inner,但由于它现在是一个对象,而是一个原语,因此newOuter.inner中的参考将被复制到outer.inner中,这意味着我们将以本地newOuter对象直接绑定到状态中的对象结束。

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

因此,Object.assign只有在你有一个相对简单的一级深度状态结构,其中最里面的成员持有原始类型的值时才会工作。

如果你有更深层次的对象(第二层或更多),你应该更新,不要使用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.immutability-helper

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(等等)它们。这里有一个可用的命令列表

一边笔记

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

对于你的项目,哪一个是正确的 ?

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

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

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

...或者,你真的<强> < / >强需要这样做吗?

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

大多数情况下,您最好将复杂的数据保存在Redux存储中,使用reducer和/或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使用一个对象。然而,概念是一样的。


你可以看到在你的终端/控制台发生了什么:

❯ 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

这真的很简单。

首先从状态中取出整个items对象,按需要更新items对象的部分,并通过setState将整个items对象放回状态。

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
}

根据React关于设置状态的文档,使用其他答案所建议的Object.assign是不理想的。由于setState的异步行为的性质,使用这种技术的后续调用可能会覆盖先前的调用,导致不希望的结果。

相反,React文档建议使用setState的更新器形式,它对前一个状态进行操作。请记住,当更新数组或对象必须返回一个新的数组或对象作为React时,要求我们保持状态不变性。使用ES6语法的展开操作符来浅复制数组,在数组的给定索引处创建或更新对象的属性将如下所示:

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

因为以上选项对我来说都不理想,所以我最终使用了map:

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");
}
}
发现这令人惊讶地困难,没有一个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 } } }))
}

改编自https://github.com/kolodny/immutability-helper#computed-property-names

是一个嵌套更复杂的对象,请根据复杂度使用适当的深度复制方法

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

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