如何从状态数组中删除项目?

275

这个故事是,我应该能够把Bob、Sally和Jack放进一个盒子里。我也可以从盒子中移除其中任何一个。当被移除时,不会留下空位。

people = ["Bob", "Sally", "Jack"]

现在我需要删除“Bob”。新的数组将是:

["Sally", "Jack"]

这是我的React组件:

...

getInitialState: function() {
  return{
    people: [],
  }
},

selectPeople(e){
  this.setState({people: this.state.people.concat([e.target.value])})
},

removePeople(e){
  var array = this.state.people;
  var index = array.indexOf(e.target.value); // Let's say it's Bob.
  delete array[index];
},

...

这里我向您展示了一个最小的代码,因为还有更多的内容(例如onClick等)。关键部分是从数组中删除、移除、销毁“Bob”,但在调用removePeople()时不起作用。有什么想法吗?我参考了这个,但由于我正在使用React,所以可能做错了什么。


问题在于您正在改变您的集合。该集合仍然指向相同的数组引用,因此它不会被视为已更改(变量仍然指向相同的数组),因此它不会重新渲染。一种常见的实现方法是复制原始状态,对其进行修改,然后使用副本覆盖当前状态,这将是一个新的数组引用,并被视为已更改,从而导致渲染更新。 - CTS_AE
18个回答

367

在使用React时,您不应该直接突变状态(state)。如果一个对象(或者是Array,因为它也是一个对象)被改变了,您应该创建一个新的副本。

有人建议使用Array.prototype.splice(),但是这种方法会改变数组,所以最好不要在React中使用splice()

最简单的方法是使用Array.prototype.filter()创建一个新的数组:

removePeople(e) {
    this.setState({people: this.state.people.filter(function(person) { 
        return person !== e.target.value 
    })});
}

71
是的,这是声明式的方法。另一种使用prevState和箭头函数的方法是:this.setState(prevState => ({ people: prevState.people.filter(person => person !== e.target.value) })); - Josh Morel
9
根据 React 的惯例永不改变状态,这应该是被接受的答案。 - lux
19
或者使用索引:this.state.people.filter((_, i) => i !== index)。该代码的作用是从 this.state.people 数组中过滤掉给定 index 的元素。 - mb21
2
有一个不可变的切片(slice)和一个可变的拼接(splice)。 - Cassian
2
这个答案的问题在于如果你有几个人有相同的名字,你会把所有这些人都删除。在可能存在重复的情况下,使用索引更安全。 - klugjo
显示剩余3条评论

295

要从数组中删除一个元素,只需执行以下操作:

array.splice(index, 1);

针对您的情况:

removePeople(e) {
  var array = [...this.state.people]; // make a separate copy of the array
  var index = array.indexOf(e.target.value)
  if (index !== -1) {
    array.splice(index, 1);
    this.setState({people: array});
  }
},

6
在我的情况下,代码是:array.splice(array, 1); - Sylar
91
在使用React时,通常应避免直接改变状态(state),而应该创建一个新的数组并使用setState()方法。 - iaretiga
4
在这种情况下,我建议使用 "Array.from(this.state.items)" 替代扩展运算符。这是因为 "Array.from" 是专门为此而设计的。 - hodgef
3
建议增加一个检查 "index !== -1",以防止意外的删除操作,在对数组进行 splice 操作之前使用该检查。 - RoboBear
5
这是反模式。splice函数会改变同一个对象。 - ibsenv
显示剩余7条评论

63

这是对Aleksandr Petrov的回答的一个小变化,使用ES6

removePeople(e) {
    let filteredArray = this.state.people.filter(item => item !== e.target.value)
    this.setState({people: filteredArray});
}

41

使用slice的简单解决方案不会改变状态

const [items, setItems] = useState(data);
const removeItem = (index) => {
  setItems([
             ...items.slice(0, index),
             ...items.slice(index + 1)
           ]);
}

23

使用.splice方法从数组中移除元素。如果使用delete,则数组的索引不会改变,但是特定索引的值将变为undefined

splice() 方法通过删除现有元素和/或添加新元素来更改数组的内容。

语法:array.splice(start, deleteCount[, item1[, item2[, ...]]])

var people = ["Bob", "Sally", "Jack"]
var toRemove = 'Bob';
var index = people.indexOf(toRemove);
if (index > -1) { //Make sure item is present in the array, without if condition, -n indexes will be considered from the end of the array.
  people.splice(index, 1);
}
console.log(people);

编辑:

正如justin-grant所指出的那样,作为一个经验法则,永远不要直接改变this.state,因为之后调用setState()可能会替换你所做的更改。把this.state当作不可变的。

另一种方法是创建对象this.state的副本并操作副本,然后使用setState()重新赋值。可以使用Array#mapArray#filter等方法。

this.setState({people: this.state.people.filter(item => item !== e.target.value);});

4
请确保不使用splice或任何直接更改状态变量的方法。相反,您需要复制数组,从副本中删除该项,然后将副本传递给setState。其他答案会详细介绍如何做到这一点。 - Justin Grant

23

filter方法是在不影响状态的情况下修改数组的最佳方式。

它根据条件返回一个新数组。

在您的情况下,filter检查条件person.id !== id并基于该条件创建一个新数组,排除符合条件的项。

const [people, setPeople] = useState(data);

const handleRemove = (id) => {
   const newPeople = people.filter((person) => person.id !== id);

   setPeople( newPeople);
 };

<button onClick={() => handleRemove(id)}>Remove</button>

不建议这样做:但如果你没有任何id,你也可以使用item index来作为条件。

index !== itemIndex


20

React中删除状态数组项的简便方法:

当从数据库中删除任何数据且在不调用API的情况下更新列表时,您可以将已删除的id传递给此函数,该函数会从列表中删除已删除的记录。

export default class PostList extends Component {
  this.state = {
      postList: [
        {
          id: 1,
          name: 'All Items',
        }, {
          id: 2,
          name: 'In Stock Items',
        }
      ],
    }


    remove_post_on_list = (deletePostId) => {
        this.setState({
          postList: this.state.postList.filter(item => item.post_id != deletePostId)
        })
      }
  
}


2
你能解释一下这与三年前的这个问题的其他8个答案有何不同吗?[来自审查] (https://stackoverflow.com/review/low-quality-posts/22538175) - Wai Ha Lee
在上述代码中,它将重新创建一个数据数组,但跳过“deletePostId”这个ID。 - ANKIT DETROJA
1
使用 item.post_id !== deletePostId - Nimish goel

5

这是您当前的状态变量:

const [animals, setAnimals] = useState(["dogs", "cats", ...])

调用此函数并传递您想要删除的项目。
removeItem("dogs")

const removeItem = (item) => {
    setAnimals((prevState) =>
      prevState.filter((prevItem) => prevItem !== item)
    );
  };

您的状态变量现在变成了:
["cats", ...]

另一种方法是使用 useState 钩子。查看文档:https://reactjs.org/docs/hooks-reference.html#functional-updates 它指出:与类组件中找到的 setState 方法不同,useState 不会自动合并更新对象。您可以通过结合函数更新程序表单和对象扩展语法来复制此行为,如下所示,或使用 useReducer 钩子。

const [state, setState] = useState({});
setState(prevState => {
  return {...prevState, ...updatedValues};
});

4

只需过滤已删除的项目,然后再次使用剩余的项目更新状态。

let remainingItems = allItems.filter((item) => {return item.id !== item_id});
    
setItems(remainingItems);

3

一些答案提到使用“splice”,这确实像Chance Smith所说的那样会改变数组。我建议您使用方法调用“slice”('slice'文档在此处),它将创建原始数组的副本。


你能提供一个最简单的例子来说明你在这里讨论的内容吗?谢谢。 - ggorlen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接