我试图通过使用这样的嵌套属性来组织我的状态:
this.state = {
someProperty: {
flag:true
}
}
但是像这样更新状态,
this.setState({ someProperty.flag: false });
是行不通的。怎样才能正确地做到这一点呢?
我试图通过使用这样的嵌套属性来组织我的状态:
this.state = {
someProperty: {
flag:true
}
}
但是像这样更新状态,
this.setState({ someProperty.flag: false });
是行不通的。怎样才能正确地做到这一点呢?
当前回答
下面是一个完整的例子,使用嵌套状态(一层)和这个答案中的解决方案,对于一个实现为类的组件:
class CaveEditModal extends React.Component {
// ...
constructor(props, context) {
super(props);
this.state = {
tabValue: '1',
isModalOpen: this.props.isModalOpen,
// ...
caveData: {
latitude: 1,
longitude: 2
}
};
// ...
const updateNestedFieldEvent = fullKey => ev => {
var [parentProperty, _key] = fullKey.split(".", 2);
this.setState({[parentProperty]: { ...this.state[parentProperty], [_key]: ev.target.value} });
};
// ...
this.handleLatitudeChange = updateNestedFieldEvent('caveData.latitude');
this.handleLongitudeChange = updateNestedFieldEvent('caveData.longitude');
}
render () {
return (
<div>
<TextField id="latitude" label="Latitude" type="number" value={this.state.caveData.latitude} onChange={this.handleLatitudeChange} />
<TextField id="longitude" label="Longitude" type="number" value={this.state.caveData.longitude} onChange={this.handleLongitudeChange} />
<span>lat={this.state.caveData.latitude} long={this.state.caveData.longitude}</span>
</div>
);
};
}
注意,状态更新器函数updateNestedFieldEvent只适用于一层嵌套对象,比如a.b,而不是a.b.c。
其他回答
我用reduce搜索做嵌套更新:
例子:
状态中的嵌套变量:
state = {
coords: {
x: 0,
y: 0,
z: 0
}
}
功能:
handleChange = nestedAttr => event => {
const { target: { value } } = event;
const attrs = nestedAttr.split('.');
let stateVar = this.state[attrs[0]];
if(attrs.length>1)
attrs.reduce((a,b,index,arr)=>{
if(index==arr.length-1)
a[b] = value;
else if(a[b]!=null)
return a[b]
else
return a;
},stateVar);
else
stateVar = value;
this.setState({[attrs[0]]: stateVar})
}
Use:
<input
value={this.state.coords.x}
onChange={this.handleTextChange('coords.x')}
/>
为了为嵌套对象设置setState,你可以遵循下面的方法,因为我认为setState不处理嵌套更新。
var someProperty = {...this.state.someProperty}
someProperty.flag = true;
this.setState({someProperty})
其思想是创建一个虚拟对象,对其执行操作,然后用更新的对象替换组件的状态
现在,展开操作符只创建对象的一层嵌套副本。如果你的状态是高度嵌套的,比如:
this.state = {
someProperty: {
someOtherProperty: {
anotherProperty: {
flag: true
}
..
}
...
}
...
}
你可以在每一层使用展开运算符setState
this.setState(prevState => ({
...prevState,
someProperty: {
...prevState.someProperty,
someOtherProperty: {
...prevState.someProperty.someOtherProperty,
anotherProperty: {
...prevState.someProperty.someOtherProperty.anotherProperty,
flag: false
}
}
}
}))
然而,随着状态嵌套越来越多,上面的语法会变得越来越糟糕,因此我建议你使用不可变性帮助包来更新状态。
关于如何使用immutability-helper更新状态,请参阅此回答。
使用箭头函数代替,这应该做的把戏。
setItems((prevState) => {
prevState.nestedData = newNestedData;
prevState.nestedData1 = newNestedData1;
});
不要忘记使用箭头函数(prevState) =>{更新js赋值语句…}
有很多库可以提供帮助。例如,使用immutability-helper:
import update from 'immutability-helper';
const newState = update(this.state, {
someProperty: {flag: {$set: false}},
};
this.setState(newState);
使用lodash/fp设置:
import {set} from 'lodash/fp';
const newState = set(["someProperty", "flag"], false, this.state);
使用lodash/fp合并:
import {merge} from 'lodash/fp';
const newState = merge(this.state, {
someProperty: {flag: false},
});
我非常认真地对待关于创建组件状态的完整副本的问题。说到这里,我强烈推荐Immer。
import produce from 'immer';
<Input
value={this.state.form.username}
onChange={e => produce(this.state, s => { s.form.username = e.target.value }) } />
这应该适用于React。PureComponent(即通过React进行浅状态比较),因为Immer巧妙地使用代理对象来有效地复制任意深度状态树。Immer也比Immutability Helper等库更加类型安全,是Javascript和Typescript用户的理想选择。
Typescript实用函数
function setStateDeep<S>(comp: React.Component<any, S, any>, fn: (s:
Draft<Readonly<S>>) => any) {
comp.setState(produce(comp.state, s => { fn(s); }))
}
onChange={e => setStateDeep(this, s => s.form.username = e.target.value)}