【问题标题】:React.js: setState overwriting, not mergingReact.js:setState 覆盖,不合并
【发布时间】:2014-09-13 21:37:49
【问题描述】:

我对 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 数据。我是否误解了这种合并的工作原理?

提前感谢您的帮助!

【问题讨论】:

  • 尝试使用 React 插件中的 immutability helpers 来以声明方式修改复杂的状态对象。

标签: javascript reactjs


【解决方案1】:

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

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 所说,您可以使用the immutability helpers 来获得您想要的效果:

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

参见this JSFiddle 示例:http://jsfiddle.net/BinaryMuse/HW6w5/

【讨论】:

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

合并是浅的,所以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 年 4 月 22 日更新

正如@sheljohn 指出的(谢谢!),在setState 中引用this.state 是不可靠的:

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

...

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

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

【讨论】:

  • 谢谢!我认为这是一个了不起的解决方案。
  • 我是新手,但这似乎是错误的; handleChange should call this.setState( (prevState, props) => ({ ... // use prevState.point }) ) 代替。
  • 天啊,我不敢相信,它就像魅力一样。你拯救了我的一天。
【解决方案3】:

类似:

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

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

【讨论】:

  • 外部的 Object.assign 并没有真正做任何事情,因为您分配的 something 属性仍然指的是原始(尽管发生了变异)对象。此外,您不应该直接改变状态中的对象。反正你打电话给setState 可能没问题,但它不干净。此外,您可以为 setState 提供部分更新,而不是完整的状态对象。
  • 再想一想:突变this.state 然后引用this.state 调用setState 绝对不行。这是因为它会破坏shouldComponentUpdate 中的代码,该代码可能会将下一个状态与前一个状态进行比较(在这种情况下总是相等的,因为两者都是对同一个对象的引用)。
猜你喜欢
  • 2012-06-12
  • 2012-11-03
  • 2011-11-24
  • 1970-01-01
  • 2018-01-18
  • 2017-11-27
  • 2014-01-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多