【问题标题】:updating list with useState more than once results in wrong output多次使用 useState 更新列表会导致错误输出
【发布时间】:2021-11-19 08:32:35
【问题描述】:

我正在努力在 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;

这是应用的实际行为

这是预期的行为

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

【问题讨论】:

    标签: javascript reactjs use-state


    【解决方案1】:

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

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

    示例:

    如果传递给ItemForm 组件的addItem 函数对itemList 的初始值有一个闭包,即一个空数组;然后是以下状态设置器函数调用:

    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]);
    }
    

    【讨论】:

      【解决方案2】:

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

      因此,在您的情况下,第二个 onAddItem 将覆盖第一个。

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

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

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

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

      【讨论】:

        【解决方案3】:

        状态更新是异步的

        对于您的两次调用,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;
        

        【讨论】:

        • 问题不在于状态更新的异步性;问题是状态在组件的特定渲染中是恒定的。这就是为什么调用onAddItem 两次会添加到 same 数组。函数 addItemitemList 的值有一个闭包,调用该函数的 same 实例会将新项目添加到它关闭的 itemList 的值中。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-04-23
        • 2019-05-03
        • 1970-01-01
        • 2021-06-19
        • 1970-01-01
        • 2020-09-28
        相关资源
        最近更新 更多