更新道具后,this.props总是=== nextProps / prevProps

时间:2014-07-28 06:14:44

标签: javascript reactjs

我在这里找到了一个关于这个问题的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不同。

6 个答案:

答案 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.myObjnextProps.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 });