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

Pic*_*els 154 javascript reactjs

我有以下状态:

this.setState({ selected: { id: 1, name: 'Foobar' } });  
Run Code Online (Sandbox Code Playgroud)

然后我更新状态:

this.setState({ selected: { name: 'Barfoo' }});
Run Code Online (Sandbox Code Playgroud)

由于setState被假定为合并,我希望它是:

{ selected: { id: 1, name: 'Barfoo' } }; 
Run Code Online (Sandbox Code Playgroud)

但相反它吃了id,状态是:

{ selected: { name: 'Barfoo' } }; 
Run Code Online (Sandbox Code Playgroud)

这是预期的行为,是什么解决方案只更新嵌套状态对象的一个​​属性?

and*_*opp 141

我认为setState()不做递归合并.

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

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

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

另一个解决方案是编写setStateRecursively()哪个在新状态上进行递归合并然后replaceState()使用它进行调用:

setStateRecursively: function(stateUpdate, callback) {
  var newState = mergeStateRecursively(this.state, stateUpdate);
  this.replaceState(newState, callback);
}
Run Code Online (Sandbox Code Playgroud)

  • 对于喜欢使用extend而且也使用babel的人来说,你可以做`this.setState({selected:{... this.state.selected,name:'barfoo'}})``它被转换为`this.setState ({selected:_extends({},this.state.selected,{name:'barfoo'})});` (111认同)
  • `this.state` [不一定是最新的](https://facebook.github.io/react/docs/component-api.html#setstate).您可以使用extend方法获得意外结果.将更新函数传递给`setState`是正确的解决方案. (14认同)
  • @Pickels这很棒,非常适合React并且不需要`underscore` /`lodash`.请把它分解成自己的答案. (8认同)
  • 如果您多次执行此操作是否可靠,或者是否有机会对replaceState调用进行排队,最后一个将获胜? (7认同)

小智 96

不动性助手最近被添加到React.addons,所以你可以做以下事情:

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

Immutability helpers文档:http: //facebook.github.io/react/docs/update.html

  • 不推荐使用update.https://facebook.github.io/react/docs/update.html (7认同)
  • @thedanotto看起来不推荐使用`React.addons.update()`方法,但替换库https://github.com/kolodny/immutability-helper包含一个等效的`update()`函数. (3认同)

bga*_*npl 34

由于许多答案使用当前状态作为合并新数据的基础,我想指出这可能会破坏.状态更改已排队,并且不会立即修改组件的状态对象.因此,在处理队列之前引用状态数据将为您提供过时数据,这些数据不会反映您在setState中所做的挂起更改.来自文档:

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

这意味着在后续调用setState时使用"当前"状态作为参考是不可靠的.例如:

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

如果需要使用当前状态(例如,将数据合并到嵌套对象中),setState或者接受函数作为参数而不是对象; 在对state进行任何先前的更新之后调用该函数,并将该状态作为参数传递 - 因此,这可以用于使原子更改保证遵守先前的更改.

  • 是否有关于如何执行此操作的示例或更多文档?afaik,没有人考虑到这一点. (4认同)
  • @Madbreaks 好的,是的,在重新阅读官方文档时,我看到指定必须使用 setState 的函数形式才能根据先前的状态可靠地更新状态。也就是说,如果不尝试在同一个函数中多次调用 setState,我迄今为止从未遇到过问题。感谢让我重新阅读规范的回复。 (2认同)

Rya*_*ton 10

我不想安装另一个库,所以这是另一个解决方案.

代替:

this.setState({ selected: { name: 'Barfoo' }});
Run Code Online (Sandbox Code Playgroud)

改为:

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

或者,感谢评论中的@ icc97,更简洁但可读性更低:

this.setState({ selected: Object.assign({}, this.state.selected, { name: "Barfoo" }) });
Run Code Online (Sandbox Code Playgroud)

另外,要明确的是,这个答案并没有违反@bgannonpl上面提到的任何问题.


Mar*_*son 7

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

Lodash示例:

this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }));
Run Code Online (Sandbox Code Playgroud)

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

this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }), () => alert(this.state.selected));
Run Code Online (Sandbox Code Playgroud)

我用过,merge因为extend丢弃了其他属性.

反应不变性的例子:

import update from "react-addons-update";

this.setState((previousState) => update(previousState, {
    selected:
    { 
        name: {$set: "Barfood"}
    }
});
Run Code Online (Sandbox Code Playgroud)