React Hooks(useState)中的Push方法是什么?

367

如何在React hook中使用useState数组推入元素?这是React状态中的旧方法吗?还是有什么新的方法?

例如,setState push example

8个回答

778
使用useState时,您可以获取状态项的更新方法。
const [theArray, setTheArray] = useState(initialArray);

然后,当您想要添加新元素时,使用该函数并传入新数组或创建新数组的函数。通常使用后者,因为状态更新是异步的,有时会批处理:

setTheArray(oldArray => [...oldArray, newElement]);

有时候,如果你仅仅在特定用户事件的处理程序中(例如click而不是mousemove)更新数组,那么你可以不使用回调函数的形式:
setTheArray([...theArray, newElement]);

React确保刷新渲染的事件是在此处列出的“离散事件”中

实时示例(将回调传递给setTheArray):

const {useState, useCallback} = React;
function Example() {
    const [theArray, setTheArray] = useState([]);
    const addEntryClick = () => {
        setTheArray(oldArray => [...oldArray, `Entry ${oldArray.length}`]);
    };
    return [
        <input type="button" onClick={addEntryClick} value="Add" />,
        <div>{theArray.map(entry =>
          <div>{entry}</div>
        )}
        </div>
    ];
}

ReactDOM.render(
    <Example />,
    document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>

因为只有在click事件(一个“离散”的事件)中对theArray进行了更新,我可以在addEntry中进行直接更新:

const {useState, useCallback} = React;
function Example() {
    const [theArray, setTheArray] = useState([]);
    const addEntryClick = () => {
        setTheArray([...theArray, `Entry ${theArray.length}`]);
    };
    return [
        <input type="button" onClick={addEntryClick} value="Add" />,
        <div>{theArray.map(entry =>
          <div>{entry}</div>
        )}
        </div>
    ];
}

ReactDOM.render(
    <Example />,
    document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>


2
如果上述方法不起作用,请尝试使用 setTheArray(currentArray => [...currentArray, newElement])。在 useEffect(...theArray..., []) 中,需要注意 theArray 是一个常量,因此需要使用函数式更新来处理未来渲染的值。 - Aprillion
1
@Aprillion - 不太确定你在说什么useEffect的例子...? - T.J. Crowder
1
如果 useEffect 的依赖列表为空 [],它将仅在第一次渲染时执行。因此,效果内部的 theArray 值始终为 initialArray。在不适用 setTheArray([...initialArray, newElement]) 的情况下,并且当 theArray 常量始终等于 initialValue 时,则需要使用函数更新。 - Aprillion
@AlwaysLearning - 官方上来说是不行的。:-) 你不能直接修改 React 状态中保存的对象。通过非常彻底的测试,在每个 React 小版本发布时重复测试,你可能可以通过直接更新后跟随 A) 调用 forceUpdate(在类组件中),或者 B) 更改其他状态成员(以触发重新渲染)来解决问题。但是,任何使用数组的组件都会优化,当数组不变时不会重新渲染(memo,...)。 - T.J. Crowder
1
感谢您提供有关离散事件的解释和参考。 - Nice Books
显示剩余6条评论

124

再详细介绍一下,这里有一些常见的例子。首先是:

const [theArray, setTheArray] = useState(initialArray);
const [theObject, setTheObject] = useState(initialObject);

在数组末尾添加元素

setTheArray(prevArray => [...prevArray, newValue])

将元素推送/更新到对象的末尾

setTheObject(prevState => ({ ...prevState, currentOrNewKey: newValue}));

在对象数组末尾推送/更新元素

setTheArray(prevState => [...prevState, {currentOrNewKey: newValue}]);

在对象数组的末尾添加元素。
let specificArrayInObject = theObject.array.slice();
specificArrayInObject.push(newValue);
const newObj = { ...theObject, [event.target.name]: specificArrayInObject };
theObject(newObj);

以下是一些可行的示例。 https://codesandbox.io/s/reacthooks-push-r991u


6
谢谢提供这些例子。我遇到问题的确切部分是你在数组对象末尾加入元素的“_Push element at end of object of arrays_”部分。我尝试了许多不同的方法,但无法拼凑在一起。 - sdouble
1
使用prevState解决了我的问题,即在同时进行数组连接和过滤时只应用最后一个调用。 - Henrique Bruno
问题在于setTheArray不会立即更新数组,在组件的handleMethod中,您无法调用theArray的值。 - assayag.org

27

您可以在自定义状态的末尾附加数据数组:

  const [vehicleData, setVehicleData] = React.useState<any[]>([]);
  setVehicleData(old => [...old, ...newArrayData]);
例如,在下面,您会看到axios的一个示例:
  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        {
          url: `http://localhost:4000/api/vehicle?page=${page + 1}&pageSize=10`,
          method: 'get',
        }
      );
      setVehicleData(old => [...old, ...result.data.data]);
    };

    fetchData();
  }, [page]);

16

最推荐的方法是同时使用包装函数和展开运算符。例如,如果您已经像这样初始化了一个名为name的状态:

const [names, setNames] = useState([])

您可以像这样将其添加到数组中:

setNames(names => [...names, newName])
希望这有所帮助。

3
请勿提供仅包含链接的答案。链接可能在将来损坏。相反,应创建一篇文章并确保实际回答问题。请不要解释,只翻译。 - BlackCetha
触发器trigger setName(names => [...names, "newval"]) onChange,但console.log(names)返回[]。为什么不立即更新? - assayag.org

7
// Save search term state to React Hooks with spread operator and wrapper function

// Using .concat(), no wrapper function (not recommended)
setSearches(searches.concat(query))

// Using .concat(), wrapper function (recommended)
setSearches(searches => searches.concat(query))

// Spread operator, no wrapper function (not recommended)
setSearches([...searches, query])

// Spread operator, wrapper function (recommended)
setSearches(searches => [...searches, query])

https://medium.com/javascript-in-plain-english/how-to-add-to-an-array-in-react-state-3d08ddb2e1dc


2

setTheArray([...theArray, newElement]);是最简单的答案,但要注意theArray中项目的变异。请使用数组项的深层克隆。


你提到的深度克隆,是指接收 objA,然后使用 UseState 和 concat 将 objA 与 objB 合并吗?和这个链接一样吗?https://gist.githubusercontent.com/kahsing/8b6701d4678aaedc71b3731bffbdea81/raw/be899c14b11587f0bcd3c6c25f7698044b60e61e/ItemList.js - Luiey
能否详细地解释一下? - famfamfam

2

我尝试使用useState将一个对象推入对象数组中,但在使用TypeScript时出现以下错误:

Type 'TxBacklog[] | undefined' must have a 'Symbol.iterator' method that returns an iterator.ts(2488)

看起来tsconfig.json的设置是正确的:

{
   "compilerOptions": {
   "target": "es6",
   "lib": [
      "dom",
      "dom.iterable",
      "esnext",
      "es6",
],

这个解决方法解决了问题(我的示例代码):
接口:
   interface TxBacklog {
      status: string,
      txHash: string,
   }

状态变量:

    const [txBacklog, setTxBacklog] = React.useState<TxBacklog[]>();

向数组中添加新对象:

    // Define new object to be added
    const newTx = {
       txHash: '0x368eb7269eb88ba86..',
       status: 'pending'
    };
    // Push new object into array
    (txBacklog) 
       ? setTxBacklog(prevState => [ ...prevState!, newTx ])
       : setTxBacklog([newTx]);

2
今日免费次数已满, 请开通会员/明日再来
   const handleAddAfterIndex = index => {
       setTheArray(oldItems => {
            const copyItems = [...oldItems];
            const finalItems = [];
            for (let i = 0; i < copyItems.length; i += 1) {
                if (i === index) {
                    finalItems.push(copyItems[i]);
                    finalItems.push(newItem);
                } else {
                    finalItems.push(copyItems[i]);
                }
            }
            return finalItems;
        });
    };

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