каков предпочтительный способ изменить состояние React?

97

Скажем, у меня есть список простых объектов, this.state.listкоторые я затем могу использовать для рендеринга списка потомков. В какой же тогда правильный способ вставить объект this.state.list?

Ниже приведен единственный способ, который, я думаю, будет работать, потому что вы не можете this.stateнапрямую изменять, как указано в документе.

this._list.push(newObject):
this.setState({list: this._list});

Мне это кажется некрасивым. Есть ли способ лучше?

Хой
источник
возможный дубликат Правильная модификация массивов состояний в ReactJS , запрос слияния
Brigand
Все эти ответы касаются добавления элемента в массив. Но как насчет удаления из него элемента? Или просто полностью сбросить массив на новый?
Августин Ридингер,

Ответы:

165

concat возвращает новый массив, поэтому вы можете сделать

this.setState({list: this.state.list.concat([newObject])});

другая альтернатива - помощник неизменяемости React

  var newState = React.addons.update(this.state, {
      list : {
        $push : [newObject]
      }
  });

  this.setState(newState);
Куча
источник
6
concatпринимает массив, поэтому вы захотите this.state.list.concat([newObject]).
Софи Альперт
@BenAlpert у меня работает в Chrome, вы говорите, что это нестандартное поведение?
Куча
6
@Heap Ну, если вы передадите ему не-массив, concatон обернет его в массив, но лучше добавить массив самостоятельно, чтобы быть явным: если у вас есть [[1,2], [3,4]]и вы хотите добавить [5,6]в качестве следующего элемента, вам нужно сделать .concat([[5,6]])еще получится [[1,2], [3,4], 5, 6].
Софи Альперт
2
Насколько я ценю идею помощников неизменяемости (и я могу в конечном итоге использовать ее), Array.concatконечно, лучше добавить еще одну библиотеку.
Шон Эркхарт 02
1
Вызов this.setState со значением, производным от this.state, приведет к возникновению проблем с пакетной обработкой обновлений. См. Stackoverflow.com/a/41445812/1998186, который ссылается на jsfiddle, показывающий возникшую у вас проблему.
NealeU 03
40

setState () можно вызвать с функцией в качестве параметра:

this.setState((state) => ({ list: state.list.concat(newObj) }))

или в ES5:

this.setState(function(state) {
  return {
   list: state.list.concat(newObj)
  }
})
Ньялаб
источник
2
@Arian: React выполнит необходимые инструкции по мутации DOM, необходимые для отображения только вновь добавленных элементов. При условии, конечно, что вы недавно добавили элементы, они не меняют способ рендеринга предыдущих элементов.
Хосе Браун
1
Это фантастический ответ, и он кажется намного чище, чем объединение массива просто для выполнения push, хотя я думаю, что это мнение.
Мэтт Стайлс,
2
@Nyalab, разве вы не должны заключать тело функции в скобки? Как сейчас в вашем примере, фигурные скобки после жирной стрелки будут начинать блок, а не объект. Что-то вроде этого:this.setState((state) => ({ list: state.list.push(newObj) }))
kumarharsh
3
Как работает этот код? метод push вернет a NUMBER, который будет установлен для listпеременной
kumarharsh
1
Вы также можете использовать знак:this.setState((state) => ({ list: [...state.list, newObj] }))
амеб
19

Обновление 2016

С ES6 вы можете использовать:

this.setState({ list: [...this.state.list, ...newObject] });
Ашиш Чаудхари
источник
5
...[newObject]то же самое, что и просто newObject.
амеб,
2
Это не сработает для пакетных обновлений. См. Мой пример на stackoverflow.com/a/41445812/1998186 .
NealeU
7

Из документации по реакции ( https://facebook.github.io/react/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous ):

Поскольку this.props и this.state могут обновляться асинхронно, вам не следует полагаться на их значения при вычислении следующего состояния.

Поэтому вам следует сделать это:

this.setState((prevState) => ({
  contacts: prevState.contacts.concat([contact])
}));
jcgzzc
источник