我有以下状态:
this.setState({ selected: { id: 1, name: 'Foobar' } });
然后我更新状态:
this.setState({ selected: { name: 'Barfoo' }});
由于setState被假定为合并,我希望它是:
{ selected: { id: 1, name: 'Barfoo' } };
但它吃了id,状态是:
{ selected: { name: 'Barfoo' } };
这是预期的行为吗?只更新嵌套状态对象的一个属性的解决方案是什么?
答案 0 :(得分:136)
我认为setState()
不会进行递归合并。
您可以使用当前状态this.state.selected
的值来构建新状态,然后在其上调用setState()
:
var newSelected = _.extend({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });
我在这里使用了函数_.extend()
函数(来自underscore.js库),以防止通过创建它的浅表副本来修改状态的现有selected
部分。
另一种解决方案是编写setStateRecursively()
,它在新状态下进行递归合并,然后用它调用replaceState()
:
setStateRecursively: function(stateUpdate, callback) {
var newState = mergeStateRecursively(this.state, stateUpdate);
this.replaceState(newState, callback);
}
答案 1 :(得分:95)
var newState = React.addons.update(this.state, {
selected: {
name: { $set: 'Barfoo' }
}
});
this.setState(newState);
Immutability helpers文档: http://facebook.github.io/react/docs/update.html
答案 2 :(得分:33)
由于许多答案使用当前状态作为合并新数据的基础,我想指出这可能会破坏。状态更改已排队,并且不会立即修改组件的状态对象。因此,在处理队列之前引用状态数据将为您提供过时数据,这些数据不会反映您在setState中所做的挂起更改。来自文档:
setState()不会立即改变this.state,但会创建挂起状态转换。在调用此方法后访问this.state可能会返回现有值。
这意味着使用"当前" state作为后续调用setState的引用是不可靠的。例如:
如果需要使用当前状态(例如,将数据合并到嵌套对象中),setState或者接受函数作为参数而不是对象;在对state进行任何先前的更新之后调用该函数,并将该状态作为参数传递 - 因此,这可以用于使原子更改保证遵守先前的更改。
答案 3 :(得分:10)
我不想安装另一个库,所以这是另一个解决方案。
而不是:
this.setState({ selected: { name: 'Barfoo' }});
请改为:
var newSelected = Object.assign({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });
或者,感谢评论中的@ icc97,更简洁,但可读性更低:
this.setState({ selected: Object.assign({}, this.state.selected, { name: "Barfoo" }) });
另外,要明确的是,这个答案并没有违反@bgannonpl上面提到的任何问题。
答案 4 :(得分:7)
基于@bgannonpl回答保留以前的状态:
Lodash 示例:
this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }));
要检查它是否正常工作,您可以使用第二个参数函数回调:
this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }), () => alert(this.state.selected));
我使用merge
因为extend
会丢弃其他属性。
React Immutability 示例:
import update from "react-addons-update";
this.setState((previousState) => update(previousState, {
selected:
{
name: {$set: "Barfood"}
}
});
答案 5 :(得分:2)
我对这种情况的解决方案是使用,如同另一个答案所指出的那样the Immutability helpers。
由于深入设置状态是一种常见情况,因此我创建了下面的mixin:
var SeStateInDepthMixin = {
setStateInDepth: function(updatePath) {
this.setState(React.addons.update(this.state, updatePath););
}
};
这个mixin包含在我的大多数组件中,我通常不再直接使用setState
。
使用此mixin,为了达到预期效果,您只需按以下方式调用函数setStateinDepth
:
setStateInDepth({ selected: { name: { $set: 'Barfoo' }}})
了解更多信息:
setStateinDepth
的参数的语法,请参阅Immutability Helpers documentation。答案 6 :(得分:1)
我正在使用es6类,我在我的顶级状态下最终得到了几个复杂的对象,并且试图使我的主要组件更加模块化,所以我创建了一个简单的类包装器来保持状态在顶层组件上但允许更多的本地逻辑。
包装器类将一个函数作为其构造函数,在主组件状态上设置属性。
export default class StateWrapper {
constructor(setState, initialProps = []) {
this.setState = props => {
this.state = {...this.state, ...props}
setState(this.state)
}
this.props = initialProps
}
render() {
return(<div>render() not defined</div>)
}
component = props => {
this.props = {...this.props, ...props}
return this.render()
}
}
然后对于top状态的每个复杂属性,我创建一个StateWrapped类。你可以在这里的构造函数中设置默认道具,它们将在初始化类时设置,你可以参考本地状态获取值并设置本地状态,引用本地函数,并将它传递给链:< / p>
class WrappedFoo extends StateWrapper {
constructor(...props) {
super(...props)
this.state = {foo: "bar"}
}
render = () => <div onClick={this.props.onClick||this.onClick}>{this.state.foo}</div>
onClick = () => this.setState({foo: "baz"})
}
那么我的顶级组件只需要构造函数将每个类设置为它的顶级状态属性,一个简单的渲染,以及任何跨组件通信的函数。
class TopComponent extends React.Component {
constructor(...props) {
super(...props)
this.foo = new WrappedFoo(
props => this.setState({
fooProps: props
})
)
this.foo2 = new WrappedFoo(
props => this.setState({
foo2Props: props
})
)
this.state = {
fooProps: this.foo.state,
foo2Props: this.foo.state,
}
}
render() {
return(
<div>
<this.foo.component onClick={this.onClickFoo} />
<this.foo2.component />
</div>
)
}
onClickFoo = () => this.foo2.setState({foo: "foo changed foo2!"})
}
似乎可以很好地满足我的目的,请记住,虽然您无法更改分配给顶级组件中的包装组件的属性的状态,因为每个包装组件正在跟踪其自己的状态但更新每次更改时顶部组件上的状态。
答案 7 :(得分:1)
编辑:此解决方案曾经使用spread syntax。目的是使对象成为对prevState
的任何引用,从而不会修改prevState
。但是在我的用法中,prevState
有时似乎已被修改。因此,为了完美克隆而没有副作用,我们现在将prevState
转换为JSON,然后再次返回。 (使用JSON的灵感来自MDN。)
记住:
setState(prevState => stateChange)
步骤
state
的根级属性 第3步和第4步可以合并为一行。
this.setState(prevState => {
var newSelected = JSON.parse(JSON.stringify(prevState.selected)) //1
newSelected.name = 'Barfoo'; //2
var update = { selected: newSelected }; //3
return update; //4
});
this.setState(prevState => {
var selected = JSON.parse(JSON.stringify(prevState.selected)) //1
selected.name = 'Barfoo'; //2
return { selected }; //3, 4
});
这很好地遵循了React准则。基于eicksl's对类似问题的回答。
答案 8 :(得分:1)
我们最初设置状态
this.setState({ selected: { id: 1, name: 'Foobar' } });
//this.state: { selected: { id: 1, name: 'Foobar' } }
我们正在更改状态对象的某个级别的属性:
const { selected: _selected } = this.state
const selected = { ..._selected, name: 'Barfoo' }
this.setState({selected})
//this.state: { selected: { id: 1, name: 'Barfoo' } }
答案 9 :(得分:0)
React状态不会在setState
中执行递归合并,同时期望不会同时进行就地状态成员更新。您必须自己复制封闭的对象/数组(使用array.slice或Object.assign)或使用专用库。
喜欢这个。 NestedLink直接支持处理复合React状态。
this.linkAt( 'selected' ).at( 'name' ).set( 'Barfoo' );
此外,指向selected
或selected.name
的链接可作为单个道具在任何地方传递,并使用set
进行修改。
答案 10 :(得分:-2)
你设置了初始状态吗?
我会使用一些自己的代码,例如:
getInitialState: function () {
return {
dragPosition: {
top : 0,
left : 0
},
editValue : "",
dragging : false,
editing : false
};
}
在我正在开发的应用程序中,这就是我设置和使用状态的方式。我相信setState
你可以随便编辑你想要的任何状态,我一直这样称呼它:
onChange: function (event) {
event.preventDefault();
event.stopPropagation();
this.setState({editValue: event.target.value});
},
请注意,您必须在React.createClass
函数中设置状态getInitialState
答案 11 :(得分:-3)
我使用tmp var来改变。
changeTheme(v) {
let tmp = this.state.tableData
tmp.theme = v
this.setState({
tableData : tmp
})
}