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

javascript reactjs

64246 观看

13回复

22125 作者的声誉

我有以下状态:

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

然后我更新状态:

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

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

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

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

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

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

作者: Pickels 的来源 发布者: 2013 年 9 月 21 日

回应 (13)


138

5127 作者的声誉

决定

我认为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);
}
作者: andreypopp 发布者: 21.09.2013 03:17

-2

556 作者的声誉

你设置了初始状态吗?

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

    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

作者: asherrard 发布者: 22.11.2013 10:29

95

959 作者的声誉

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

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

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

作者: user3874611 发布者: 07.09.2014 06:21

-3

59 作者的声誉

我使用tmp var来改变。

changeTheme(v) {
    let tmp = this.state.tableData
    tmp.theme = v
    this.setState({
        tableData : tmp
    })
}
作者: hkongm 发布者: 06.07.2015 09:34

2

1914 作者的声誉

对于这种情况,我的解决方案就是像另一个答案所指出的那样使用Immutability助手

由于设置状态是一个常见的情况,我创建了下面的mixin:

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 文档
作者: Dorian 发布者: 30.10.2015 08:38

34

376 作者的声誉

由于许多答案使用当前状态作为合并新数据的基础,我想指出这可能会破坏。状态更改已排队,并且不会立即修改组件的状态对象。因此,在处理队列之前引用状态数据将为您提供过时数据,这些数据不会反映您在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进行任何先前的更新之后调用该函数,并将该状态作为参数传递 - 因此,这可以用于使原子更改保证遵守先前的更改。

作者: bgannonpl 发布者: 16.12.2015 08:14

1

0 作者的声誉

我正在使用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()
    }
}

然后对于顶级状态的每个复杂属性,我创建一个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!"})
}

对于我的目的来说似乎工作得很好,请记住,虽然你不能改变你在顶级组件中分配给包装组件的属性的状态,因为每个包装组件都跟踪它自己的状态但是更新顶层组件上的状态每次改变。

作者: user1641172 发布者: 08.05.2016 02:37

7

4390 作者的声誉

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

Lodash示例:

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

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

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

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

反应不变性的例子:

import update from "react-addons-update";

this.setState((previousState) => update(previousState, {
    selected:
    { 
        name: {$set: "Barfood"}
    }
});
作者: Martin Dawson 发布者: 23.08.2016 06:12

0

1686 作者的声誉

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

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

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

此外,链接到selectedselected.name可以作为单个道具随处传递并在那里进行修改set

作者: gaperton 发布者: 19.04.2017 07:58

10

7042 作者的声誉

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

代替:

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上面提到的任何问题。

作者: Ryan Shillington 发布者: 21.07.2017 07:13

2

1260 作者的声誉

截至目前,

如果下一个状态取决于先前的状态,我们建议使用更新程序功能表单,而不是:

根据文档https://reactjs.org/docs/react-component.html#setstate,使用:

this.setState((prevState) => {
    return {quantity: prevState.quantity + 1};
});
作者: egidiocs 发布者: 26.11.2017 01:10

1

71 作者的声誉

编辑:此解决方案用于使用扩展语法。目标是创建一个没有任何引用的对象prevState,这样prevState就不会被修改。但在我的使用中,prevState似乎有时会被修改。因此,对于没有副作用的完美克隆,我们现在转换prevState为JSON,然后再返回。(使用JSON的灵感来自MDN。)

记得:

脚步

  1. 制作要更改的根级属性的副本state
  2. 改变这个新对象
  3. 创建更新对象
  4. 返回更新

步骤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对类似问题回答。

作者: Tim 发布者: 30.07.2018 06:36

1

4125 作者的声誉

ES6解决方案

我们最初设定了州

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' } }
作者: Roman 发布者: 19.12.2018 11:22
32x32