使用useState从数组中删除最后一个项目

8

我对JS、React和TypeScript都比较新,我做了一个添加todo-list的教程。为了练习,我决定添加删除按钮和“删除最后一项”按钮。

删除整个列表很顺利(我为自己感到骄傲,哈哈!),但是“删除最后一项”无法正常工作,我尝试了不同的方法(例如仅使用todos.pop())。


function App() {
  const [todos, setTodos] = useState([])
  const [input, setInput] = useState("")

  // prevents default, adds input to "todos" array and removes the input from form
  const addTodo = (e) => {
    e.preventDefault()

    setTodos([...todos, input])
    setInput("")
  }

  // deletes the todo-list
  const clearTodo = (e) => {
    e.preventDefault()

    setTodos([])
  }

  // deletes the last entry from the todo-list
  const clearLastTodo = (e) => {
    e.preventDefault()

    setTodos(todos.pop())
  }

  return (
    <div className="App">
      <h1>ToDo Liste</h1>
      <form>
        <input 
          value={input} 
          onChange={(e) => setInput(e.target.value)} 
          type="text" 
        />
        <button type="submit" onClick={addTodo}>
          Hinzufügen
        </button>
      </form>

      <div>
        <h2>Bisherige ToDo Liste:</h2>
        <ul>
        {todos.map((todo) => (
          <li>{todo}</li>
        ))}
        </ul>
      </div>

      <div>
        <form action="submit">
        <button type="submit" onClick={clearLastTodo}>
            Letzten Eintrag löschen
          </button>
          <button type="submit" onClick={clearTodo}>
            Liste löschen
          </button>
        </form>
      </div>
    </div>
  );
}

export default App;

我有什么遗漏吗(显然我有,否则它就可以工作了)?但是是什么呢?:D
提前感谢!
3个回答

14

你的问题有三种可能的解决方案。


解决方案1: 从第一个元素切片到倒数第二个元素(倒数第一个元素的前一个元素)。

setTodos(todos.slice(0, -1)));

或者
setTodos((previousArr) => (previousArr.slice(0, -1)));

解决方案2: 创建一个复制数组并使用-1的值对其进行切片。然后将该数组从第一个元素设置到-1个元素。

const copyArr = [...todos];
copyArr.splice(-1);
setTodos(copyArr);

解决方案3: 创建一个带有 ... 的复制列表,弹出该复制并将数组的新值设置为该复制。
const copyArr = [...todos];
copyArr.pop();
setTodos(copyArr);

你的回答过于复杂了。我建议坚持你最初的回调解决方案。这是最好的解决方案。splicepop只会用不太理想的替代方案污染这个答案。 - 3limin4t0r
你是对的。但是把所有答案都给出来,让任何人自己选择不是更好吗? - Itik
这是解决方案1中展示的解决方案。 - Itik
@Itik 如果其他答案已经提供了这些解决方案,那么就不需要再提供了。此外,最好每个答案只提供一个解决方案。这样人们可以对每个单独的解决方案进行投票,而不是一组解决方案。如果您有多个解决方案,可以将它们作为单独的答案发布。 - 3limin4t0r
@3limin4t0r 哦,我明白你的意思了。谢谢。 - Itik
显示剩余2条评论

4
你可以按照以下方式完成。扩展运算符确保你不会将相同的引用传递给 setTodos:
const clearLastTodo = (e) => {
    e.preventDefault();
    let copy = [...todos]; // makes sure you don't give the same reference to setTodos
    copy.pop()
    setTodos(copy); 
  }

React中关于状态的一般注释:

始终将 state 变量视为不可变。对于像数字、布尔值这样的原始值很明显,但是对于对象和数组,改变它们的内容并不会使它们变得不同,应该是一个新的内存引用


根据我的理解:这段代码之所以能够正常工作,是因为我先使用 pop() 方法弹出了待办事项,然后再使用 setTodos 函数更新了列表。否则,它只会弹出待办事项,而不会在屏幕上更新列表。当我尝试使用 setTodos(todos.pop()) 时,它虽然移除了最后一项,但由于我使用了 .pop() 方法并且它返回了被移除的值,所以它立即又被 setTodos() 函数设置回去了,对吗? - justsomeron
它不会更新屏幕上的 todos,因为 React 应用程序认为对 useState 所做的更改不需要重新渲染。你可以做的是切片数组:todos.slice(0, todos.length - 1))。这将从第一个元素切片到倒数第二个元素(倒数第一前面的元素)。 - Itik
@justsomeron,是的,todos.pop() 返回最后一个被移除的元素,而你想要的是一个数组。当你执行 todos.pop() 时,你的 todos 数组会被更新,但为了让 React 重新渲染,你需要一个带有新引用的 setTodos,这就是为什么使用扩展运算符,它会在内存中的新位置上复制 todos - Youssouf Oumar
谢谢。我看到你已经更新了关于复制版本的答案。在todos.pop()setTodo([todos...])之前的那个版本运行良好。为什么现在要使用复制版本呢?抱歉,我不明白。 - justsomeron
哈哈哈...你说得对...抱歉我多嘴了。 - Louys Patrice Bessette
显示剩余4条评论

1

有几种方法可以在javascript中从数组中删除最后一个项目。

const arr = [1, 2, 3, 4, 5]
arr.splice(-1) // -> arr = [1,2,3,4]
// or
arr.pop()      // -> arr = [1,2,3,4]
// or
const [last, ...rest] = arr.reverse()
const removedLast = rest.reverse()

根据指南更新状态中的对象和数组

const onRemoveLastTodo = () => {
    // Try with each way above
  setTodos(todos.splice(-1))
}

嘿,todos.splice(-1) 在我有三个元素时删除了前两个元素。不确定为什么,所以这不起作用。我之前也在自己的测试中测试过 :) - justsomeron
splice 会改变原始数组。你看到的可能是 splice 函数的结果 :))。再试一次吧。 - nart

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