После обновления реквизита this.props всегда === nextProps/prevProps

У меня есть jsbin для этой проблемы здесь: http://jsbin.com/tekuluve/1/edit

В событии onClick я удаляю элемент из модели и повторно рендерирую приложение. Но как ни странно, в компонентахWillReceiveProps() (и 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
);

Мне нужно, чтобы nextProps отличались от this.props после того, как он был обновлен через UpdateModel(), в событии lifecycle компонента componentWillReceiveProps().

Ответы

Ответ 1

=== проверяет, является ли он одним и тем же объектом. Кажется, что то, что вы делаете, изменяет значение, на которое указывает свойство boxes.

Ответ 2

Что-то вроде этого произошло для меня, используя Flux Stores, чтобы сохранить мое состояние в соответствии с официальным руководством Todo List (http://facebook.github.io/flux/docs/todo-list.html), Для других, кто нашел это после выполнения учебника, проблема возникает в TodoStore в методе getAll(), потому что она возвращает прямую ссылку на внутренний объект данных:

getAll: function() {
  return _todos;
}

Это, по-видимому, нарушает способность методов жизненного цикла, таких как componentDidUpdate (prevProps), различать старый и новый реквизиты. Я считаю, что причина заключается в том, что, передавая прямую ссылку на объект данных Store в представлениях, состояние/реквизит эффективно меняются сразу же после изменения Хранилища, а не после того, как новые значения передаются по методам жизненного цикла, поэтому старые и новые реквизиты всегда содержат те же значения. Это можно решить, передав копию внутреннего объекта данных _todos, а не самого объекта,

например, когда _todos является объектом,

getAll: function() {
  return JSON.parse(JSON.stringify(_todos));
}

Если _todos является массивом, вместо него можно использовать _todos.slice().

В общем случае, если вы используете Store для хранения информации о состоянии и вызывают его из представлений вашего контроллера, представляется целесообразным вернуть копию данных, а не ссылку на оригинал, поскольку оригинал будет мутирован при изменении состояния происходят.

Ответ 3

Вы можете использовать Immutable.js. Это библиотека, которую Facebook сделал для работы с реакцией/потоком. https://facebook.github.io/immutable-js/

Мы несколько раз сталкивались с этим.

shouldComponentUpdate(nextProps) { 
    return this.props.myObj !== nextProps.myObj
}

Много раз, некоторое значение внутри объекта myObj изменилось бы. Однако вышеприведенная функция вернет false. Причина в том, что оба this.props.myObj и nextProps.myObj ссылались/указывали на один и тот же объект.

Внедряя Immutable.js, данные всегда передаются вниз как клоны (вместо ссылки на один и тот же объект они фактически будут отдельными объектами).

Он повторно вводит однонаправленный поток потока. Вы никогда не сможете (случайно) изменить исходные данные, которые передаются в ваш компонент (будь то реквизит или из хранилища потоков).

Это также позволяет использовать PureRenderMixin - который автоматически останавливает рендер, если значения состояния/реквизита не изменились. Это может помочь в производительности.

Ответ 4

Одной из альтернатив реализации 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. Я нашел, что оператор спреда будет особенно полезен при изменении состояния в редукторах редукции. Если вы установите переменные равными состоянию напрямую и внесите изменения, это вызовет проблему, с которой вы столкнулись.

Ответ 5

Если вы не хотите использовать распространение, вы можете это сделать. Просто измените вашу функцию UpdateModel на

UpdateModel(){
  React.renderComponent(
    <Grid boxes={ _.clone(_.pull(model, _.sample(model))) } />, document.body
  );
}

В противном случае объект nextProp.boxes будет тем же объектом, что и объект this.props.boxes, поэтому любые изменения, внесенные вами в него, будут отражены в обоих.