反应:如何更新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.assign
, cloneDeep
-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
上的事件来找出已更改的元素,然后进行更新。 为此,您可能需要更改某个属性来识别并更新它。
尝试这将明确的工作,其他情况下,我试过,但没有工作
import _ from 'lodash'; this.state.var_name = _.assign(this.state.var_name, { obj_prop: 'changed_value', });
如何创build另一个组件(用于需要进入数组的对象)并将以下内容作为道具传递?
- 组件索引 – 索引将用于在数组中创build/更新。
- 设置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)