多次使用 useState 更新列表会导致输出错误

updating list with useState more than once results in wrong output

我正在为在 React 中使用 useState 处理列表而苦苦挣扎。我必须连续两次向列表中添加一个项目,并且只添加了第二个项目。代码:

App.js

import React, { useState } from "react";
import ItemForm from "./itemForm";

function App() {
  const [itemList, setItemList] = useState([]);

  function addItem(item) {
    setItemList([...itemList, item]);
  }

  return (
    <div>
      <ItemForm onAddItem={ addItem } />
      { itemList && 
          itemList.map((item, i) => <ul key={ i + itemList } >{ item }</ul>) }
    </div>
  );
}

export default App;

ItemForm.js

import React, { useState } from 'react';

function ItemForm({ onAddItem }) {
    const [inputValue, setInputValue] = useState('');

    function handleSubmit(e) {
        e.preventDefault();
        var item = e.target.item.value;
        // Here, I have to call onAddItem twice.
        onAddItem(item);
        onAddItem(item + " second time");
        
    }

    return (
        <div>
            <form onSubmit={ handleSubmit } >
                <input value={ inputValue }  
                onChange={(e) => setInputValue(e.target.value) }  name="item"></input>
                <button type="submit">Add</button>
            </form>
        </div>
    )
}

export default ItemForm;

这是应用的实际行为

这是预期的行为

为什么会发生这种情况,我该如何解决?

状态更新是异步的

对于您的两次调用,itemsList 数组完全相同。 :

假设您有一个数组:[1,2,3]。您的代码实际上所做的是第一次使它成为 [1,2,3,4],第二次使它成为 [1,2,3,5]

setItemList([...itemList, item]) //[1,2,3,4]
setItemList([...itemList, item]) //[1,2,3,5]

你的数组在第二次更新时仍然使用原来的值。

执行此操作的一种方法是使用使用先前状态的模式。这是使用先前状态的可靠方法。

import React, { useState } from "react";
import ItemForm from "./itemForm";

function App() {
  const [itemList, setItemList] = useState([]);

  function addItem(item) {
    setItemList(itemList => ([...itemList, item]));
  }

  return (
    <div>
      <ItemForm onAddItem={ addItem } />
      { itemList && 
          itemList.map((item, i) => <ul key={ i + itemList } >{ item }</ul>) }
    </div>
  );
}

export default App;
import React, { useState } from 'react';

function ItemForm({ onAddItem }) {
    const [inputValue, setInputValue] = useState('');

    function handleSubmit(e) {
        e.preventDefault();
        var item = e.target.item.value;
        // Here, I have to call onAddItem twice.
        onAddItem(item);
        onAddItem(item + " second time");
        
    }

    return (
        <div>
            <form onSubmit={ handleSubmit } >
                <input value={ inputValue }  
                onChange={(e) => setInputValue(e.target.value) }  name="item"></input>
                <button type="submit">Add</button>
            </form>
        </div>
    )
}

export default ItemForm;

React 不会在您调用 setState 后立即更新状态,它需要所有 setState 调用,并在重新渲染之前运行它们。

所以在你的情况下,第二个 onAddItem 会覆盖第一个。

要解决此类问题,您需要更改代码中的一些逻辑,例如:

  function addItem(...newItems) {
    setItemList(previousItemList => ([...previousItemList, ...newItems]));
  }

现在您可以传递多个项目来添加项目并且只调用一次。

 onAddItem(item, item + " second time");

作为 prop 传递给 ItemForm 组件的函数,即 onAddItem,在创建该函数时生效的 itemList 值上有一个闭包。

因此,使用 item 的不同值调用该函数两次会创建一个新数组,其 itemList 值相同 addItem 函数关闭。

示例:

如果传递给ItemForm组件的addItem函数在itemList的初始值上有一个闭包,即一个空数组;然后下面的状态setter函数调用:

setItemList([...itemList, item]);

展开空数组,然后添加新的 item,如下所示。

setItemList([...[], item]);

两次调用addItem函数等同于:

setItemList([...[], item]);
setItemList([...[], item]);

另请注意,状态在组件的特定渲染中是恒定的。组件在重新呈现之前看不到更新后的状态。

解决方案

你可以传递一个函数给setItemList,根据itemList之前的值更新状态:

function addItem(item) {
   setItemList(currentState => [...currentState, item]);
}

另一种选择是将数组传递给 onAddItem 函数:

onAddItem([item1, item2]);

并将 addItem 函数更改为:

function addItem(itemsArr) {
   setItemList([...itemList, ...itemsArr]);
}