React.js:setState 覆盖,而不是合并

IT技术 javascript reactjs
2021-03-24 11:45:52

我是 React.JS 的新手,我正在通过构建砖石风格的布局进行实验。

我将每个元素渲染到 DOM,然后我需要遍历每个项目并根据前面的元素应用 x 和 y 位置。

初始模型如下所示:

[
  {
    "title": "The Forrest",
    "description": "some cool text",
    "imgSmallSrc": "/img/img4-small.jpg",
    "imgAlt": "Placeholder image",
    "tags": [
        "Design",
        "Mobile",
        "Responsive"
    ],
    "date": 1367154709885,
    "podStyle": {
      "width": 253
    }
  }
]

(为了简短起见,我只展示了一项)。

完成循环并获得 x 和 y 数据后,我想将其应用于podStyle对象。我调用setState()以下数据:

[
  {
    "podStyle": {
      "x": 0,
      "y": 0,
      "height": 146,
      "width": 253
    }
  }
]

这似乎从模型中删除了所有当前数据,而只留下podStyle数据。我误解了这种合并的工作原理吗?

在此先感谢您的帮助!

3个回答

如果您的状态是一个对象:

getInitialState: function() {
  return { x: 0, y: 0 };
}

您可以使用setState在该对象上设置单个键:

this.setState({ x: 1 }); // y still == 0

React 不会智能合并您的状态;例如,这不起作用:

getInitialState: function() {
  return {
    point: { x: 0, y: 0 },
    radius: 10
  };
}

this.setState({point: {x: 1}});
// state is now == {point: {x: 1}, radius: 10} (point.y is gone)

[编辑]

正如@ssorallen 所提到的,您可以使用不变性助手来获得您想要的效果:

var newState = React.addons.update(this.state, {
  point: { x: {$set: 10} }
});
this.setState(newState);

这个 JSFiddle为例:http : //jsfiddle.net/BinaryMuse/HW6w5/

所以我很确定我正在按照您的第二个示例设置状态,但是,在这种情况下,“半径”正在被删除。当我今晚回家时,我会四处玩耍并确保我的设置正确。感谢您的帮助布兰登!
2021-05-28 11:45:52
非常感谢您的帮助布兰登。这绝对让我朝着正确的方向前进。我最终使用了 $merge 助手,因为我的模型略有不同,而且我还将更新应用于几个子项。不过效果很好。
2021-06-03 11:45:52
@DanV 这是一个 JSFiddle,用于演示我的答案中的代码。jsfiddle.net/BinaryMuse/HW6w5我还添加了一个按钮来使用 ssorallen 提到的不变性助手,它可以做你想做的事情。
2021-06-19 11:45:52

合并很浅,所以this.setState({point})留下 (ed: this.state.radius) 完好无损,但完全取代 (ed: this.state.point)。

https://facebook.github.io/react/docs/state-and-lifecycle.html#state-updates-are-merged

为了对已经给出的答案提供 ES7+ 观点,使用transform-object-rest-spread而不是Object.assign()

class MyComponent extends React.Component {
    state = {
        point: { 
            x: 0, 
            y: 0,
        },
        radius: 10,
    }

    handleChange = () => {
        this.setState((prevState, props) => ({
            point: {
                // rest operator (...) expands out to:
                ...prevState.point, // x:0, y:0,
                y: 1, // overwrites old y
            },
            // radius is not overwritten by setState
        }));
    }

    render() {
        // omitted
    }
}

.babelrc(还需要来自 babel 预设阶段 2 的transform-class-properties

{
    "presets": ["es2015", "stage-2", "react"],
    "plugins": ["transform-object-rest-spread"],
}

2018-04-22 更新

正如@sheljohn 指出的(谢谢!),提到this.stateinsidesetState是不可靠的:

因为this.propsthis.state可能会异步更新,所以您不应该依赖它们的值来计算下一个状态。

...

要修复它,请使用setState()接受函数而不是对象的第二种形式该函数将接收先前的状态作为第一个参数,并将应用更新时的 props 作为第二个参数

https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous

我是新手,但这似乎是错误的;handleChange 应该打电话 this.setState( (prevState, props) => ({ ... // use prevState.point }) )
2021-06-02 11:45:52
天哪,我简直不敢相信,它就像魅力一样。你救了我的一天。
2021-06-17 11:45:52
谢谢!我认为这是一个了不起的解决方案。
2021-06-20 11:45:52

就像是:

getInitialState: function() {
    return {
        something: { x: 0, y: 0 },
        blah: 10
    };
}

var state = Object.assign(this.state, {
    something: Object.assign(this.state.something, { y: 50 }),
});

this.setState(state);

如果它是递归/深度而不是对树进行硬编码会更好,但我会将其留给读者:)

关于它的思考多一点:这绝对不是正常变异this.state,然后调用setState同一个参考this.state这是因为它会破坏代码,因为它shouldComponentUpdate可能会将下一个状态与前一个状态进行比较(在这种情况下,这将始终相等,因为两者都是对同一对象的引用)。
2021-06-06 11:45:52
外部Object.assign实际上并没有做任何事情,因为something您分配属性仍然是指原始(尽管已发生变异)对象。此外,您不应该直接改变状态中的对象。setState反正你打电话可能没问题,但它不干净。此外,您可以提供部分更新setState而不是完整状态对象。
2021-06-12 11:45:52