Замена объекта в массиве в состоянии реакции

#javascript #arrays #reactjs #ecmascript-6 #immutability

#javascript #массивы #reactjs #ecmascript-6 #неизменность

Вопрос:

Этот вопрос может немного отличаться от вопроса «наилучшей практики», но, пожалуйста, потерпите меня.

Вот часть моего состояния:

 this.state = {
  typeElements: {
    headers: [
        {
          name: "h1",
          size: 70,
          lineHeight: 1.25,
          kearning: 0,
          marginAfter: 0
        }, {
          name: "h2",
          size: 70,
          lineHeight: 1.25,
          kearning: 0,
          marginAfter: 0
        }, {
          name: "h3",
          size: 70,
          lineHeight: 1.25,
          kearning: 0,
          marginAfter: 0
        }...
  

Что мне нужно сделать, это ЗАМЕНИТЬ объект с заданным индексом в массиве заголовков.

Я не знаю, как это сделать с помощью метода setState, как в this.setState(headers[1] = {obj}) — но это, очевидно, недопустимо. Мой текущий метод создает новый массив и удаляет старый следующим образом:

 _updateStyle(props) {
  let newState = Object.assign({}, this.state)
  newState.typeElements.headers[props.index] = props
  this.setState(newState)
};
  

Для моего небольшого хакерского проекта, я думаю, все в порядке, но я чувствую, что это очень сложно и быстро приведет к проблемам с производительностью в любом масштабе.

Ответ №1:

Обновлено: поскольку этот ответ по-прежнему получает голоса, имейте в виду, что предыдущий ответ ниже устарел для современных JavaScript и React. Аддон «обновление» теперь является устаревшим, и вместо него можно использовать «помощник неизменности».

В документах React также упоминается, почему важна неизменность, поэтому избегайте изменения состояния. Для неизменяемых обновлений вы можете использовать Object.assign() или распространять синтаксис, который необходимо выполнить для каждого уровня вложенности, как в этом примере вложенный headers объект и его элементы массива. В этом конкретном примере мы можем использовать индекс массива в качестве ключа, поэтому можно также использовать оператор spread для создания мелкого клонирования массива и присвоения нового объекта в качестве значения по заданному индексу в клонированном массиве.

 _updateStyle (props) {
  const { typeElements } = this.state;
  const updatedHeaders = [...typeElements.headers];
  updatedHeaders[props.index] = props;
  this.setState({
    ...this.state,
    typeElements: {
      ...typeElements,
      headers: updatedHeaders
    }
  ));
}
  

Другое решение, которое не требует расширенного синтаксиса и необходимо, если мы не используем индекс массива для поиска объекта, который мы хотим заменить, — это использование array.map для создания нового массива и возврата нового объекта вместо старого с заданным индексом.

   const updatedHeaders = typeElements.headers.map((obj, index) => {
    return index === props.index ? props : obj;
  });
  

Аналогичные примеры в документах Redux также объясняют «неизменяемые шаблоны обновления».

Для этого у React есть несколько помощников неизменности, что объясняется в документах: https://facebook.github.io/react/docs/update.html

В вашем случае вы могли бы использовать команду $splice, чтобы удалить один элемент и добавить новый с заданным индексом, например:

 _updateStyle (props) {
  this.setState(update(this.state.typeElements, 
    { $splice: [[props.index, 1, props]] }
  ));
}
  

Комментарии:

1. ага, я знал об этом, но не был уверен, как это реализовать, так что это происходит в методе setState? Что делает «1»? Это точка вставки нового объекта? Не мог бы я просто снова использовать props.index для этого, если это так?

2. Проверьте метод array.splice() developer.mozilla.org/nl/docs/Web/JavaScript/Reference /… и ты поймешь… массив содержит аргументы, переданные методу splice, поэтому «1» в этом примере является значением deleteCount .

Ответ №2:

Предлагая лучшее объяснение того, как это сделать.

  • Сначала найдите индекс элемента, который вы заменяете, в массиве состояний.
  • Во-вторых, update элемент с этим индексом
  • В-третьих, вызовите setState новую коллекцию
 import update from 'immutability-helper';

// this.state = { employees: [{id: 1, name: 'Obama'}, {id: 2, name: 'Trump'}] } 

updateEmployee(employee) {
    const index = this.state.employees.findIndex((emp) => emp.id === employee.id);
    const updatedEmployees = update(this.state.employees, {$splice: [[index, 1, employee]]});  // array.splice(start, deleteCount, item1)
    this.setState({employees: updatedEmployees});
}
  

Ответ №3:

используйте помощник неизменности

вы можете найти там хорошие примеры

Ответ №4:

Объект.Assign использует мелкую копию, а не глубокую копию.

Пожалуйста, имейте в виду, что в вашем примере Object.assign({}, this.state) копируются только ссылки на ближайшие дочерние элементы state , т. Е. На typeElements , Но headers массив не копируется.

В ES6 есть синтаксический сахар ... для объекта.Назначьте, у Babel есть специальный аддон transform-object-rest-spread .

 let newHeaders = { ...this.state.typeElements.headers};
newHeaders[index] = props.Something;

let newState = {...state, typeElements: { newHeaders }};
  

Комментарии:

1. Действительно? На самом деле он тоже скопировал заголовки для меня. Можете ли вы уточнить?

2. @motleydev Метод Object.assign() копирует только перечислимые и собственные свойства из исходного объекта в целевой объект (MDN developer.mozilla.org/en-US/docs/Web/JavaScript/Reference /… ). Это означает, что он копирует только ссылки на ближайшие объекты или значения для примитивов (чисел, строк). Вы получаете старый массив в своей реализации, и если вы его измените, это также повлияет на другие места, где он используется.