反应:如何更新setState上的state.item ? (用JSFiddle)

我正在创build一个应用程序,用户可以devise自己的表单。 例如,指定字段的名称以及应该包含哪些其他列的详细信息。

该组件在这里可以用作JSFiddle。

我的初始状态如下所示:

77 var DynamicForm = React.createClass({ 78 getInitialState: function() { 79 var items = {}; 80 items[1] = { name: 'field 1', populate_at: 'web_start', 81 same_as: 'customer_name', 82 autocomplete_from: 'customer_name', title: '' }; 83 items[2] = { name: 'field 2', populate_at: 'web_end', 84 same_as: 'user_name', autocomplete_from: 'user_name', title: '' }; 85 86 return { items }; 87 }, 108 render: function() { 109 var _this = this; 110 return ( 111 <div> 112 { Object.keys(this.state.items).map(function (key) { 113 var item = _this.state.items[key]; 114 return ( 115 <div> 120 <PopulateAtCheckboxes this={this} 121 checked={item.populate_at} id={key} populate_at={data.populate_at} /> 129 </div> 130 ); 131 }, this)} 132 <button onClick={this.newFieldEntry}>Create a new field</button> 133 <button onClick={this.saveAndContinue}>Save and Continue</button> 134 </div> 135 ); 136 } 

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

  19 var PopulateAtCheckboxes = React.createClass({ 20 handleChange: function (e) { 21 item = this.state.items[1]; 22 item.name = 'newName'; 23 items[1] = item; 24 25 this.setState({items: items}); 26 }, 27 28 render: function() { 29 var populateAtCheckbox = this.props.populate_at.map(function(value) { 30 return ( 31 <label for={value}> 32 <input type="radio" name={'populate_at'+this.props.id} value={value} 33 onChange={this.handleChange} checked={this.props.checked == value} 34 ref="populate-at"/> 35 {value} 36 </label> 37 ); 38 }, this); 39 return ( 40 <div className="populate-at-checkboxes"> 41 {populateAtCheckbox} 42 </div> 43 ); 44 } 45 }); 

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

使用ES6:

 handleChange(e) { const items = this.state.items; items[1].role = e.target.value; // update state this.setState({ items, }); }, 

或者,如果您直接导出您的课程:

 handleChange = (e) => { const items = this.state.items; items[1].role = e.target.value; // update state this.setState({ items, }); }; 

更新!

正如评论中的许多更好的开发人员所指出的那样:改变状态是错误的。 不需要使用“setState”,因为你修改了一个子值。 这个工作的唯一原因是因为“setState”做了一个重新渲染视图的“forceUpdate”。

所以最好这样做:

 handleChange = (e) => { const items = this.state.items; items[1].role = e.target.value; // re-render this.forceUpdate(); }; 

这样它仍然会改变你的价值,并用新的数据重新渲染视图。

你可以使用update不可变性帮助器 :

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

或者,如果您不关心在使用===shouldComponentUpdate()生命周期方法中检测到对此项目的更改,则可以直接编辑状态并强制组件重新呈现 – 这与@看点“的答案,因为它正在将一个对象从状态中拉出来进行编辑。

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

编辑后添加:

查看简单组件通信课程,了解如何将callback函数从持有状态的父级传递给需要触发状态更改的子组件的示例。

首先得到你想要的物品,在那个物品上改变你想要的东西,然后把它放在状态上。 如果您使用键控对象,只通过在getInitialState传递对象来使用状态的方式会更容易。

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

不要改变现状。 它会导致意想不到的结果。 我已经吸取了教训! 总是使用复制/克隆, 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

编辑:

对于深层克隆:

 const groups = JSON.parse(JSON.stringify(this.state.groups[index])) 

一个现实世界的例子与ES6:

 handleChange(index, e) { const groups = [...this.state.groups]; groups[index].name = e.target.value; this.setState({ groups }); } 

如前所述,为了修改React状态下的深层嵌套对象/variables,通常使用三种方法:来自Lodash的 vanilla JS Object.assigncloneDeep -helper和cloneDeep 。

正如我在评论中指出一些作者提出的国家直接变异的答案, 就是不这样做 。 这是一个无处不在的反应模式,这将不可避免地导致不必要的后果。 学习正确的方法。

现在,我们来比较一下前面提到的方法。

鉴于这种状态结构:

 state = { foo: { bar: 'initial value' } }; 

使用vanilla JavaScript( Object.assign )可以像这样完成最里面的bar更改:

 (...essential imports) class App extends Component { state = { foo: { bar: 'initial value' } }; componentDidMount() { console.log(this.state.foo.bar); // initial value const foo = Object.assign({}, this.state.foo, {bar: 'further value'}); console.log(this.state.foo.bar); // initial value this.setState({foo}, () => { console.log(this.state.foo.bar); // further value }); } (...rest of code) 

请记住, Object.assign不会执行深层克隆,因为它只复制属性值 ,这就是为什么它称为浅拷贝 (请参阅注释)。 如果你有一个相对简单的一层深层次的状态结构,最内层的成员持有原始types的值,那么它就可以工作。 像这个例子中的bar一样,拿着string。

如果你有更深的对象,你应该更新,不要使用Object.assign 。 你直接冒险改变状态。

现在,Lodash的( cloneDeep ):

 (...essential imports) import {cloneDeep} from 'lodash'; class App extends Component { state = { foo: { bar: 'initial value' } }; componentDidMount() { console.log(this.state.foo.bar); // initial value const foo = cloneDeep(this.state.foo); foo.bar = 'further value'; console.log(this.state.foo.bar); // initial value this.setState({foo}, () => { console.log(this.state.foo.bar); // further value }); } (...rest of code) 

基本上,Lodash的cloneDeep执行深度克隆,所以如果你有一个相当复杂的状态,包含多级对象或数组,那么它是一个强大的选项。

最后,这是如何完成与不可变性帮手

 (...essential imports) import update from 'immutability-helper'; class App extends Component { state = { foo: { bar: 'initial value' } }; componentDidMount() { console.log(this.state.foo.bar); // initial value const foo = update(this.state.foo, {bar: {$set: 'further value'}}); console.log(this.state.foo.bar); // initial value this.setState({foo}, () => { console.log(this.state.foo.bar); // further value }); } (...rest of code) 

除了$set$push$splice$merge等之外,还有许多不变的辅助函数可用 。

请注意, this.setState()只修改状态对象的第一层属性 (在这种情况下是foo属性),而不是深层嵌套的( foo.bar )。 如果performance其他方式,这个问题就不存在了。

顺便说一句, this.setState({foo})只是this.setState({foo: foo})的缩写。 在{foo} () => {console.log(this.state.foo.bar)}之后,和() => {console.log(this.state.foo.bar)}setState已经设置状态之后立即执行。 方便,如果你需要做一些事情后做它的工作。

现在,当三种主要方法都被覆盖的时候,我们来比较它们的速度。 我为此创build了一个jsperf。 在这个testing中,具有10000个具有随机值的属性的巨大状态被上述所有三种方法操纵。

以下是我的Windows 10上的Chrome 61.0.3163的结果:

 +---------------------+---------+-----------+-------------+ | Test | Ops/sec | Precision | Explanation | +---------------------+---------+-----------+-------------+ | Object.assign | 272 | ±0.62% | 56% slower | | cloneDeep | 618 | ±0.69% | fastest | | immutability-helper | 271 | ±0.33% | 56% slower | +---------------------+---------+-----------+-------------+ 

请注意,在同一台机器上的边缘Lodashperformance比其他两种方法更差。 在Firefox中有时候cloneDeep是一个胜利者,但在其他时候则是Object.assign

这里是jsperf.comtesting的链接 。

如果你不想或者可以使用外部依赖,并且有一个简单的状态结构,那么坚持Object.assign 。 如果你操纵一个巨大的和/或复杂的状态,Lodash的cloneDeep是一个明智的select。 如果你需要先进的function,也就是说,如果你的状态结构比较复杂,而且你需要对它进行各种操作,那么尝试immutability-helper ,这是一个非常先进的工具,可以用来进行状态操作。

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

看小提琴https://jsfiddle.net/69z2wepo/6164/

尝试这将明确的工作,其他情况下,我试过,但没有工作

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

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

  1. 组件索引 – 索引将用于在数组中创build/更新。
  2. 设置function – 该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})}/> 

我将移动function句柄更改并添加一个索引参数

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

到dynamic表单组件并将其作为一个道具传递给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"/> 

如果您只需要更改数组的一部分,那么您的状态将会设置为

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

最好是更新数组中的红色,如下所示

  const newItems = this.state.items .filter((item) => item.name === 'red-one') .map(item => item.value = 666) this.setState(newItems) 

无变异:

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