Удаление элемента из массива в состоянии компонента

131

Я пытаюсь найти лучший способ удалить элемент из массива в состоянии компонента. Поскольку мне не следует изменять this.stateпеременную напрямую, есть ли лучший (более краткий) способ удалить элемент из массива, чем тот, который у меня здесь ?:

  onRemovePerson: function(index) {
    this.setState(prevState => { // pass callback in setState to avoid race condition
      let newData = prevState.data.slice() //copy array from prevState
      newData.splice(index, 1) // remove element
      return {data: newData} // update state
    })
  },

Спасибо.

обновленный

Это было обновлено для использования обратного вызова в setState. Это следует делать при обращении к текущему состоянию при его обновлении.

aherriot
источник
Взгляните на ImmutableJS от Facebook, который хорошо работает с React. ссылка
Джонатан Лундквист Меден
6
Я не вижу ничего плохого в вашем коде. На самом деле это очень идиоматический способ сделать это.
Димитар Димитров

Ответы:

138

Самый чистый способ сделать это, что я видел, - это filter:

removeItem(index) {
  this.setState({
    data: this.state.data.filter((_, i) => i !== index)
  });
}
ephrion
источник
18
@HussienK _иногда используется для представления неиспользованного аргумента. Здесь это текущий элемент в массиве.
chrisM
1
Удивительный. Я использую .filter все время, но никогда не думал использовать его в этой ситуации. : D
dakt
4
Это чистый код, однако для больших массивов этот метод медленный. Причина в том, что он просматривает весь массив, чтобы найти индекс, который, похоже, уже определен.
Мэтт Эллис,
1
@MattEllis Поскольку обновления состояния React в любом случае неизменяемы, вы в любом случае собираетесь получить O (n) в размере списка, чтобы скопировать его. Я был бы удивлен, если бы это сильно снизило производительность.
ephrion
4
Не рекомендуется использовать даже неизменяемую оценку в this.stateкачестве входных данных this.setState(). пожалуйста, посмотрите мой ответ выше, где есть ссылка на официальную документацию React по этой теме.
pscl
87

Вы можете использовать update()помощник неизменяемости fromreact-addons-update , который фактически делает то же самое под капотом, но то, что вы делаете, нормально.

this.setState(prevState => ({
  data: update(prevState.data, {$splice: [[index, 1]]})
}))
Джонни Бьюкенен
источник
Гораздо более простой пример, чем тот, на который вы ссылались :)
Коэн.
7
react-addons-updateустарела (2016). immutability-helperдоступен в качестве замены. github.com/kolodny/immutability-helper Также см. мой ответ ниже о том, что не следует изменять this.state непосредственно внутри this.setState ().
pscl
62

Я считаю, что ссылки this.stateвнутри setState()не рекомендуется ( обновления состояния могут быть асинхронными ).

Документы рекомендуют использовать setState()с функцией обратного вызова, чтобы prevState передавался во время выполнения, когда происходит обновление. Вот как это будет выглядеть:

Использование Array.prototype.filter без ES6

removeItem : function(index) {
  this.setState(function(prevState){
    return { data : prevState.data.filter(function(val, i) {
      return i !== index;
    })};
  });
}

Использование Array.prototype.filter с функциями стрелок ES6

removeItem(index) {
  this.setState((prevState) => ({
    data: prevState.data.filter((_, i) => i !== index)
  }));
}

Использование неизменяемости-помощника

import update from 'immutability-helper'
...
removeItem(index) {
  this.setState((prevState) => ({
    data: update(prevState.data, {$splice: [[index, 1]]})
  }))
}

Использование спреда

function removeItem(index) {
  this.setState((prevState) => ({
    data: [...prevState.data.slice(0,index), ...prevState.data.slice(index+1)]
  }))
}

Обратите внимание, что в каждом экземпляре, независимо от используемой техники, this.setState()передается обратный вызов, а не ссылка на старый объект this.state;

pscl
источник
3
Это правильный ответ, см. Также «Сила неизменности данных»
Винни Джеймс
1
Примеры использования обратного вызова prevState с добавлением различных техник.
pscl
что, если я сделаю это так? this.setState({ data: [...this.state.data.slice(0, index), ...this.state.data.slice(index + 1)] }); неправильно ли использовать this.stateвместо prevStateопции обратного вызова, показанной @ user1628461?
Тибериу Максим
Это лучшее решение, хотя и не самое простое
Developia
спасибо, используя распространение, можно также обновить элемент посередине вместо его удаления, это то, что мне нужно.
Vaibhav Vishal
24

Вот способ удалить элемент из массива в состоянии с использованием синтаксиса распространения ES6.

onRemovePerson: (index) => {
  const data = this.state.data;
  this.setState({ 
    data: [...data.slice(0,index), ...data.slice(index+1)]
  });
}
evianpring
источник
1
Спасибо! Это кажется наиболее естественным способом сделать это.
fabiomaia 08
3

Я хочу поговорить здесь, хотя на этот вопрос уже был правильно дан ответ от @pscl на случай, если кто-то еще столкнется с той же проблемой, что и я. Из 4 предложенных методов я решил использовать синтаксис es6 со стрелочными функциями из-за его краткости и отсутствия зависимости от внешних библиотек:

Использование Array.prototype.filter с функциями стрелок ES6

removeItem(index) {
  this.setState((prevState) => ({
    data: prevState.data.filter((_, i) => i != index)
  }));
}

Как видите, я сделал небольшую модификацию, чтобы игнорировать тип индекса ( !==чтобы!= ), потому что в моем случае я получал индекс из строкового поля.

Еще один полезный момент, если вы видите странное поведение при удалении элемента на стороне клиента, - НИКОГДА не использовать индекс массива в качестве ключа для элемента :

// bad
{content.map((content, index) =>
  <p key={index}>{content.Content}</p>
)}

Когда React отличается от виртуальной DOM при изменении, он будет смотреть на ключи, чтобы определить, что изменилось. Поэтому, если вы используете индексы, а в массиве на один меньше, он удалит последний. Вместо этого используйте идентификаторы контента в качестве ключей, как это.

// good
{content.map(content =>
  <p key={content.id}>{content.Content}</p>
)}

Вышеупомянутая выдержка из этого ответа из соответствующего сообщения .

Всем удачного кодирования!

c0d3ster
источник
1

Вы можете использовать эту функцию, если хотите удалить элемент (без индекса)

removeItem(item) {
  this.setState(prevState => {
    data: prevState.data.filter(i => i !== item)
  });
}
Джулиан Либор
источник
1

Как упоминалось в комментарии к ответу ephrion выше, filter () может работать медленно, особенно с большими массивами, поскольку он выполняет цикл для поиска индекса, который, по-видимому, уже определен. Это чистое, но неэффективное решение.

В качестве альтернативы можно просто «вырезать» нужный элемент и объединить фрагменты.

var dummyArray = [];    
this.setState({data: dummyArray.concat(this.state.data.slice(0, index), this.state.data.slice(index))})

Надеюсь это поможет!

Мэтт Эллис
источник
0

Вы можете сделать код более читаемым с помощью однострочной вспомогательной функции:

const removeElement = (arr, i) => [...arr.slice(0, i), ...arr.slice(i+1)];

затем используйте его так:

this.setState(state => ({ places: removeElement(state.places, index) }));
Брайан Бернс
источник
0

Просто предложение, в вашем коде вместо использования let newData = prevState.dataвы можете использовать распространение, представленное в ES6, то есть вы можете использоватьlet newData = ...prevState.data для копирования массива

Три точки ... обозначают операторы распространения или параметры отдыха ,

Он позволяет раскрывать выражение массива или строку или что-либо, что может повторяться в местах, где ожидается ноль или более аргументов для вызовов функций или элементов для массива.

Кроме того, вы можете удалить элемент из массива следующим образом:

onRemovePerson: function(index) {
  this.setState((prevState) => ({
    data: [...prevState.data.slice(0,index), ...prevState.data.slice(index+1)]
  }))
}

Надеюсь, это поможет !!

Саддам Камал
источник
-3

Вот простой способ сделать это:

removeFunction(key){
  const data = {...this.state.data}; //Duplicate state.
  delete data[key];                  //remove Item form stateCopy.
  this.setState({data});             //Set state as the modify one.
}

Надеюсь, поможет!!!

T04435
источник
Не хочешь объяснить?
Тибериу Максим
5
Я думаю, это связано с тем, что deleteэлемент удаляется, но индексы не обновляются, и поэтому этот подход не подходит для обычных нужд.
Kunok