多次使用 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]);
}
我正在为在 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]);
}