我在这里找到了一个关于这个问题的jsbin:http://jsbin.com/tekuluve/1/edit
在onClick事件中,我正在从模型中删除元素,并重新呈现应用程序。但奇怪的是,在componentWillReceiveProps()(以及componentWillUpdate和componentDidUpdate)中,无论我做什么,nextProps总是===到this.props。
/** @jsx React.DOM */
var Box = React.createClass({
render: function() {
return (
<div className="box" onClick={ UpdateModel }>
{ this.props.label }
</div>
);
}
});
var Grid = React.createClass({
componentWillReceiveProps: function(nextProps) {
// WTF is going on here???
console.log(nextProps.boxes === this.props.boxes)
},
render: function() {
var boxes = _.map(this.props.boxes, function(d) {
return (<Box label={ d.number } />);
});
return (
<div className="grid">
{ boxes }
</div>
);
}
});
var model = [
{ number: 1 },
{ number: 2 },
{ number: 3 },
{ number: 4 },
{ number: 5 }
];
function UpdateModel() {
React.renderComponent(
<Grid boxes={ _.pull(model, _.sample(model)) } />,
document.body
);
}
React.renderComponent(
<Grid boxes={ model } />,
document.body
);
在componentWillReceiveProps()生命周期事件中通过UpdateModel()更新后,我需要nextProps与this.props不同。
答案 0 :(得分:14)
使用Flux Stores以官方Todo List教程(http://facebook.github.io/flux/docs/todo-list.html)建议的方式存储我的状态时,发生了类似这样的事情。对于在完成本教程后发现这个问题的其他人,getAll()方法中的TodoStore似乎出现问题,因为它返回对内部数据对象的直接引用:
getAll: function() {
return _todos;
}
这似乎打破了生命周期方法(如componentDidUpdate(prevProps))区分旧道具和新道具的能力。我认为原因是通过将对Store的数据对象的直接引用传递到视图中,state / props在Store更改时立即有效地更改,而不是在通过生命周期方法传递新值之后立即更改,因此新旧道具总是如此包含相同的值。这可以通过传递内部数据对象_todos的副本而不是对象本身来解决,
例如,当_todos是一个对象时,
getAll: function() {
return JSON.parse(JSON.stringify(_todos));
}
如果_todos是一个数组,则可以使用return _todos.slice()代替。
通常,如果使用Store来包含状态信息并从控制器视图中调用它,则建议返回数据的副本而不是对原始数据的引用,因为在状态更改时原始数据将被突变发生。
答案 1 :(得分:13)
===
将检查它是否是同一个对象。看来你正在做的是改变boxes
属性指向的值。
答案 2 :(得分:9)
您可以使用Immutable.js。它是Facebook与react / flux合作的一个库。 https://facebook.github.io/immutable-js/
我们已经被这几次困扰了。
shouldComponentUpdate(nextProps) {
return this.props.myObj !== nextProps.myObj
}
很多时候,myObj对象中的某些值会发生变化。但是上面的函数会返回false。原因是this.props.myObj
和nextProps.myObj
都引用/指向同一个对象。
通过实现Immutable.js,数据将始终作为克隆传递(而不是引用相同的对象,它们实际上是单独的对象)。
它重新强化了助长剂的单向流动。您将永远无法(意外地)修改传递到组件中的原始数据(无论是作为道具还是来自助焊剂存储区)。
这也使您可以使用PureRenderMixin - 如果状态/道具的值没有改变,它会自动停止渲染。这有助于提高绩效。
答案 3 :(得分:0)
实现Immutable.js的一种替代方法是使用对象扩展运算符。
举个例子,
假设您有一个要修改的变量,然后使用
执行某些操作const obj1 = {a: 1, b: 2}
const obj2 = testObj
obj2.a: 3
// result: obj1 = obj2 = {a:3, b:2}
将修改原始对象。这是因为obj2变量本质上只是指向obj1对象的指针。
现在假设我们使用扩展运算符
const obj1 = {a: 1, b: 2}
const obj2 = {...testObj}
obj2.a: 3
// result: obj1 = {a:1, b:2}, obj2 = {a:3, b:2}
这样做的结果是obj2已正确更新,但obj1保持不变。扩散算子有效地创建了obj1的浅层克隆。我发现扩展运算符在修改redux reducer中的状态时特别有用。如果您将变量直接设置为等于状态并进行修改,则会导致您遇到的问题。
答案 4 :(得分:0)
如果您不想使用传播,则可以改为使用传播。只需将UpdateModel
功能更改为
UpdateModel(){
React.renderComponent(
<Grid boxes={ _.clone(_.pull(model, _.sample(model))) } />, document.body
);
}
否则,nextProp.boxes对象与this.props.boxes对象具有相同的对象,因此您对它所做的任何更改都将反映在两者中。
答案 5 :(得分:0)
最后我修复了它。 问题是在不深入克隆的情况下更改了道具。
我的道具是一个array of objects
,我正在使用:
let newtodos = [...this.state.todos];
newtodos[0].text = 'changed text';
this.setState({ todos : newtodos });
所以在componentDidUpdate
中,我得到了错误的结果:
componentDidUpdate(prevProps){
//prevProps.todos is always equal to this.props.todos, since you changed the prop object directly.
if(JSON.stringify(prevProps.todos) !== JSON.stringify(this.props.todos){...}
}
所以我用:
// let newtodos = [...this.state.todos]; // SHALLOW COPY - Wrong
let newtodos = JSON.parse(JSON.stringify(this.state.todos)); // Deep Clone - Right
newtodos[0].text = 'changed text';
this.setState({ todos : newtodos });