[英]this.setState isn't merging states as I would expect
I have the following state:我有以下状态:
this.setState({ selected: { id: 1, name: 'Foobar' } });
Then I update the state:然后我更新状态:
this.setState({ selected: { name: 'Barfoo' }});
Since setState
is suppose to merge I would expect it to be:由于
setState
应该合并,我希望它是:
{ selected: { id: 1, name: 'Barfoo' } };
But instead it eats the id and the state is:但相反,它吃掉了 id,状态是:
{ selected: { name: 'Barfoo' } };
Is this expected behavior and what's the solution to update only one property of a nested state object?这是预期的行为吗?仅更新嵌套状态对象的一个属性的解决方案是什么?
I think setState()
doesn't do recursive merge.我认为
setState()
不会进行递归合并。
You can use the value of the current state this.state.selected
to construct a new state and then call setState()
on that:您可以使用当前状态
this.state.selected
的值来构造一个新状态,然后在其上调用setState()
:
var newSelected = _.extend({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });
I've used function _.extend()
function (from underscore.js library) here to prevent modification to the existing selected
part of the state by creating a shallow copy of it.我在这里使用了函数
_.extend()
函数(来自 underscore.js 库)通过创建它的浅拷贝来防止修改状态的现有selected
部分。
Another solution would be to write setStateRecursively()
which does recursive merge on a new state and then calls replaceState()
with it:另一种解决方案是编写
setStateRecursively()
,它对新状态进行递归合并,然后用它调用replaceState()
:
setStateRecursively: function(stateUpdate, callback) {
var newState = mergeStateRecursively(this.state, stateUpdate);
this.replaceState(newState, callback);
}
Immutability helpers were recently added to React.addons, so with that, you can now do something like:不变性助手最近添加到 React.addons 中,因此,您现在可以执行以下操作:
var newState = React.addons.update(this.state, {
selected: {
name: { $set: 'Barfoo' }
}
});
this.setState(newState);
Since many of the answers use the current state as a basis for merging in new data, I wanted to point out that this can break.由于许多答案使用当前状态作为合并新数据的基础,我想指出这可能会中断。 State changes are queued, and do not immediately modify a component's state object.
状态更改排队,并且不会立即修改组件的状态对象。 Referencing state data before the queue has been processed will therefore give you stale data that does not reflect the pending changes you made in setState.
因此,在处理队列之前引用状态数据将为您提供不反映您在 setState 中所做的未决更改的陈旧数据。 From the docs:
从文档:
setState() does not immediately mutate this.state but creates a pending state transition.
setState() 不会立即改变 this.state 而是创建一个挂起的状态转换。 Accessing this.state after calling this method can potentially return the existing value.
调用此方法后访问 this.state 可能会返回现有值。
This means using "current" state as a reference in subsequent calls to setState is unreliable.这意味着在对 setState 的后续调用中使用“当前”状态作为参考是不可靠的。 For example:
例如:
If you need to use the current state (eg to merge data into a nested object), setState alternatively accepts a function as an argument instead of an object;如果您需要使用当前状态(例如将数据合并到嵌套对象中),setState 或者接受一个函数作为参数而不是一个对象; the function is called after any previous updates to state, and passes the state as an argument -- so this can be used to make atomic changes guaranteed to respect previous changes.
该函数在任何先前的状态更新后被调用,并将状态作为参数传递——因此这可用于保证原子更改尊重先前的更改。
I didn't want to install another library so here's yet another solution.我不想安装另一个库,所以这是另一个解决方案。
Instead of:代替:
this.setState({ selected: { name: 'Barfoo' }});
Do this instead:改为这样做:
var newSelected = Object.assign({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });
Or, thanks to @icc97 in the comments, even more succinctly but arguably less readable:或者,感谢评论中的@icc97,更简洁但可以说可读性更差:
this.setState({ selected: Object.assign({}, this.state.selected, { name: "Barfoo" }) });
Also, to be clear, this answer doesn't violate any of the concerns that @bgannonpl mentioned above.另外,要明确的是,这个答案没有违反@bgannonpl 上面提到的任何问题。
Preserving the previous state based on @bgannonpl answer:保留基于@bgannonpl 答案的先前状态:
Lodash example:洛达什示例:
this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }));
To check that it's worked properly, you can use the second parameter function callback:要检查它是否正常工作,您可以使用第二个参数函数回调:
this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }), () => alert(this.state.selected));
I used merge
because extend
discards the other properties otherwise.我使用了
merge
因为否则extend
会丢弃其他属性。
React Immutability example:反应不变性示例:
import update from "react-addons-update";
this.setState((previousState) => update(previousState, {
selected:
{
name: {$set: "Barfood"}
}
});
As of right now,截至目前,
If the next state depends on the previous state, we recommend using the updater function form, instead:
如果下一个状态依赖于前一个状态,我们建议使用 updater 函数形式,而不是:
according to documentation https://reactjs.org/docs/react-component.html#setstate , using:根据文档https://reactjs.org/docs/react-component.html#setstate ,使用:
this.setState((prevState) => {
return {quantity: prevState.quantity + 1};
});
My solution for this kind of situation is to use, like another answer pointed out, the Immutability helpers .我对这种情况的解决方案是使用Immutability helpers ,就像另一个答案指出的那样。
Since setting the state in depth is a common situation, I've created the folowing mixin:由于深度设置状态是一种常见情况,因此我创建了以下 mixin:
var SeStateInDepthMixin = {
setStateInDepth: function(updatePath) {
this.setState(React.addons.update(this.state, updatePath););
}
};
This mixin is included in most of my components and I generally do not use setState
directly anymore.这个 mixin 包含在我的大多数组件中,我通常不再直接使用
setState
。
With this mixin, all you need to do in order to achieve the desired effect is to call the function setStateinDepth
in the following way:有了这个mixin,你需要做的就是以如下方式调用函数
setStateinDepth
来达到预期的效果:
setStateInDepth({ selected: { name: { $set: 'Barfoo' }}})
For more information:了解更多信息:
setStateinDepth
see the Immutability Helpers documentation .setStateinDepth
的参数的语法,请参阅 Immutability Helpers文档。I am using es6 classes, and I ended up with several complex objects on my top state and was trying to make my main component more modular, so i created a simple class wrapper to keep the state on the top component but allow for more local logic.我正在使用 es6 类,最终在我的顶层状态中有几个复杂的对象,并试图使我的主要组件更加模块化,所以我创建了一个简单的类包装器来保持顶层组件上的状态,但允许更多的本地逻辑.
The wrapper class takes a function as its constructor that sets a property on the main component state.包装器类将一个函数作为其构造函数,该函数在主组件状态上设置一个属性。
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()
}
}
Then for each complex property on the top state, i create one StateWrapped class.然后对于顶部状态的每个复杂属性,我创建一个 StateWrapped 类。 You can set the default props in the constructor here and they will be set when the class is initialised, you can refer to the local state for values and set the local state, refer to local functions, and have it passed up the chain:
您可以在此处的构造函数中设置默认道具,它们将在类初始化时设置,您可以引用本地状态的值并设置本地状态,引用本地函数,并将其传递到链中:
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"})
}
So then my top level component just needs the constructor to set each class to it's top level state property, a simple render, and any functions that communicate cross-component.那么我的顶级组件只需要构造函数来将每个类设置为它的顶级状态属性、一个简单的渲染以及任何与跨组件通信的函数。
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!"})
}
Seems to work quite well for my purposes, bear in mind though you can't change the state of the properties you assign to wrapped components at the top level component as each wrapped component is tracking its own state but updating the state on the top component each time it changes.似乎对我的目的来说效果很好,但请记住,尽管您无法更改分配给顶级组件的包装组件的属性状态,因为每个包装组件都在跟踪自己的状态,但会更新顶级组件的状态每次都变了。
Edit: This solution used to use spread syntax .编辑:此解决方案用于使用传播语法。 The goal was make an object without any references to
prevState
, so that prevState
wouldn't be modified.目标是创建一个不引用
prevState
的对象,这样就不会修改prevState
。 But in my usage, prevState
appeared to be modified sometimes.但是在我的使用中,
prevState
似乎有时会被修改。 So, for perfect cloning without side effects, we now convert prevState
to JSON, and then back again.因此,为了完美克隆而没有副作用,我们现在将
prevState
转换为 JSON,然后再返回。 (Inspiration to use JSON came from MDN .) (使用 JSON 的灵感来自MDN 。)
Remember:记住:
setState(prevState => stateChange)
setState(prevState => stateChange)
Steps脚步
state
that you want to changestate
的根级属性Steps 3 and 4 can be combined on one line.步骤 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
});
This follows the React guidelines nicely.这很好地遵循了 React 指南。 Based on eicksl's answer to a similar question.
基于eicksl对类似问题的回答。
We set the state initially我们最初设置状态
this.setState({ selected: { id: 1, name: 'Foobar' } });
//this.state: { selected: { id: 1, name: 'Foobar' } }
We are changeing a property on some level of the state object:我们正在更改状态对象某个级别的属性:
const { selected: _selected } = this.state
const selected = { ..._selected, name: 'Barfoo' }
this.setState({selected})
//this.state: { selected: { id: 1, name: 'Barfoo' } }
React state doesn't perform the recursive merge in setState
while expects that there won't be in-place state member updates at the same time. React 状态不会在
setState
执行递归合并,同时预计不会有就地状态成员更新。 You either have to copy enclosed objects/arrays yourself (with array.slice or Object.assign) or use the dedicated library.您必须自己复制封闭的对象/数组(使用 array.slice 或 Object.assign)或使用专用库。
Like this one.像这个。 NestedLink directly supports handling of the compound React state.
NestedLink直接支持处理复合 React 状态。
this.linkAt( 'selected' ).at( 'name' ).set( 'Barfoo' );
Also, the link to the selected
or selected.name
can be passed everywhere as a single prop and modified there with set
.此外,指向
selected
或selected.name
的链接可以作为单个 prop 传递到任何地方,并在那里使用set
修改。
have you set the initial state?你设置初始状态了吗?
I'll use some of my own code for example:我将使用一些我自己的代码,例如:
getInitialState: function () {
return {
dragPosition: {
top : 0,
left : 0
},
editValue : "",
dragging : false,
editing : false
};
}
In an app I'm working on, this is how I've been setting and using state.在我正在开发的应用程序中,这就是我设置和使用状态的方式。 I believe on
setState
you can then just edit whatever states you want individually I've been calling it like so:我相信在
setState
您可以单独编辑您想要的任何状态,我一直这样称呼它:
onChange: function (event) {
event.preventDefault();
event.stopPropagation();
this.setState({editValue: event.target.value});
},
Keep in mind you have to set the state within the React.createClass
function that you called getInitialState
请记住,您必须在您调用
getInitialState
的React.createClass
函数中设置状态
I use the tmp var to change.我使用 tmp var 进行更改。
changeTheme(v) {
let tmp = this.state.tableData
tmp.theme = v
this.setState({
tableData : tmp
})
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.