this.setState没有像我期望的那样合并状态

我有以下状态:

this.setState({ selected: { id: 1, name: 'Foobar' } }); 

然后我更新状态:

 this.setState({ selected: { name: 'Barfoo' }}); 

由于setState是假设合并我期望它是:

 { selected: { id: 1, name: 'Barfoo' } }; 

但相反,它吃的ID和国家是:

 { selected: { name: 'Barfoo' } }; 

这是预期的行为和有什么解决scheme来更新只有一个嵌套状态对象的属性?

我认为setState()不会执行recursion合并。

你可以使用当前状态的值this.state.selected构造一个新的状态,然后调用setState()

 var newSelected = _.extend({}, this.state.selected); newSelected.name = 'Barfoo'; this.setState({ selected: newSelected }); 

我在这里使用了函数_.extend()函数(来自underscore.js库),通过创build它的浅表副本来防止修改现有selected的状态部分。

另一个解决scheme是编写setStateRecursively() ,它在新状态下进行recursion合并,然后用它调用replaceState()

 setStateRecursively: function(stateUpdate, callback) { var newState = mergeStateRecursively(this.state, stateUpdate); this.replaceState(newState, callback); } 

React.addons最近添加了不可变性助手,所以现在可以执行如下操作:

 var newState = React.addons.update(this.state, { selected: { name: { $set: 'Barfoo' } } }); this.setState(newState); 

不变性助手文档: http : //facebook.github.io/react/docs/update.html

由于许多答案使用当前状态作为合并新数据的基础,因此我想指出这可能会打破。 状态更改已排队,并且不会立即修改组件的状态对象。 因此,在处理队列之前引用状态数据会给你提供的数据不能反映你在setState中做出的未决修改。 从文档:

setState()不会立即改变this.state,但会创build一个挂起的状态转换。 调用此方法后访问this.state可能会返回现有的值。

这意味着在随后的setState调用中使用“当前”状态作为参考是不可靠的。 例如:

  1. 首先调用setState,排队更改状态对象
  2. 第二次调用setState。 你的状态使用嵌套的对象,所以你想执行一个合并。 在调用setState之前,你会得到当前的状态对象。 这个对象并不反映上面第一次调用setState所做的排队更改,因为它仍然是原来的状态,现在应该被认为是“过时的”。
  3. 执行合并。 结果是原始的“陈旧”状态加上刚刚设置的新数据,不会反映从初始setState调用的更改。 您的setState调用队列这第二个更改。
  4. React进程队列。 首先调用setState调用,更新状态。 第二个setState调用被处理,更新状态。 第二个setState的对象现在已经取代了第一个,并且由于您在进行该调用时所拥有的数据是陈旧的,所以来自第二个调用的修改后的陈旧数据已经对第一次调用中所做的更改进行了修改,而这些更改已丢失。
  5. 当队列为空时,React决定是否渲染等。此时你将渲染在第二个setState调用中所做的更改,就好像第一个setState调用从未发生过一样。

如果您需要使用当前状态(例如将数据合并到嵌套对象中),则setState接受函数作为参数而不是对象; 在之前的任何更新状态之后调用该函数,并将该状态作为parameter passing – 因此,可以使用此方法来保证primefaces更改能够保证尊重以前的更改。

我不想安装另一个库,所以这是另一个解决scheme。

代替:

 this.setState({ selected: { name: 'Barfoo' }}); 

做这个,而不是:

 var newSelected = Object.assign({}, this.state.selected); newSelected.name = 'Barfoo'; this.setState({ selected: newSelected }); 

保留基于@bgannonpl的以前的状态回答:

Lodash例子:

 this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfoo"} })); 

要检查它是否正常工作,可以使用第二个参数函数callback:

 this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfoo"} }), () => alert(this.state.selected)); 

我使用merge因为否则放弃其他属性。

React不变性例子:

 import update from "react-addons-update"; this.setState((previousState) => update(previousState, { selected: { name: {$set: "Barfoo"} } }); 

对于这种情况,我的解决方法就是像另一个答案一样, 使用不变性帮助者 。

由于深度设置状态是一种常见的情况,我创build了如下的混合:

 var SeStateInDepthMixin = { setStateInDepth: function(updatePath) { this.setState(React.addons.update(this.state, updatePath);); } }; 

这个mixin包含在我的大部分组件中,我通常不直接使用setState

使用这个mixin,为了达到预期的效果,你只需要按照下面的方式调用setStateinDepth函数:

 setStateInDepth({ selected: { name: { $set: 'Barfoo' }}}) 

了解更多信息:

  • 关于mixin如何在React中工作,请参阅官方文档
  • 有关传递给setStateinDepth的参数的语法,请参阅Immutability Helpers 文档 。

我正在使用es6类,我结束了我的顶部状态的几个复杂的对象,并试图使我的主要组件更模块化,所以我创build了一个简单的类包装保持顶部组件的状态,但允许更多的本地逻辑。

包装类采用一个函数作为其构造函数,在主要组件状态上设置一个属性。

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

然后,对于顶部状态的每个复杂属性,我创build一个StateWrapped类。 你可以在这里设置构造函数中的默认道具,当类被初始化的时候,它们会被设置,你可以参考本地状态值来设置本地状态,参考本地函数,并且把它传递给链:

 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!"}) } 

似乎工作相当好为我的目的,请记住,尽pipe您不能更改分配给顶层组件的包装组件的属性的状态,因为每个包装组件正在跟踪其自己的状态,但更新顶部组件的状态每次它改变。

React状态不会在setState执行recursion合并,同时预期不会有就地状态成员更新。 您必须自己复制封闭的对象/数组(使用array.slice或Object.assign)或使用专用库。

像这个。 NestedLink直接支持处理复合React状态。

 this.linkAt( 'selected' ).at( 'name' ).set( 'Barfoo' ); 

此外, selectedselected.name的名称的链接可以作为单个道具传递,并在那里进行修改。

你有没有设置初始状态?

我将使用一些我自己的代码,例如:

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

请记住,您必须在您调用getInitialStateReact.createClass函数内设置状态

我使用tmp var来改变。

 changeTheme(v) { let tmp = this.state.tableData tmp.theme = v this.setState({ tableData : tmp }) }