React State 仅在设置无用状态变量以及必要的状态变量时更新
Posted
技术标签:
【中文标题】React State 仅在设置无用状态变量以及必要的状态变量时更新【英文标题】:React State only updates when setting a useless state variable along with the necessary state variable 【发布时间】:2021-07-11 14:27:03 【问题描述】:状态定义如下:
const [items, setItems] = useState([] as CartItemType[]);
const [id, setId] = useState<number | undefined>();
在这种情况下,id
完全没用。在我的应用中根本不需要它。
但是,如果我尝试更新 items
,状态变量不会改变并且 UI 不会重新加载,除非我也会更新 id
:
useEffect(() => console.log("reload")); // only fires if I include setId
const clickItem = (item: CartItemType) =>
let tempItems = data;
// @ts-ignore
tempItems[item.id - 1].animation =
"item animate__animated animate__zoomOut";
setItems(tempItems!); // "!" to get rid of ts complaint about possible undefined value
setId(item.id); // nothing happens if I don't include this
;
// ... inside the return, in a map fn
<Item
item=item
handleAddToCart=handleAddToCart
clickItem=clickItem
/>
// inside Item component
<StyledItemWrapper
className=item.animation
onClick=() =>
clickItem(item); // item = an obj containing an id and an animation property
>
这里为什么需要setId
? setItems
不是在做什么?
【问题讨论】:
您是否尝试在 setItems([...tempItems]) 上强制使用新数组;或 Array.from(tempItems)。您在项目渲染功能中使用什么作为键? 使用@ts-ignore
和!
非空断言违背了使用TypeScript 的目的。您应该尝试修复代码而不是消除错误。
这能回答你的问题吗? How do I update states onchange in an array of object in React Hooks
@EmileBergeron 我同意 - 正在与时间赛跑。
【参考方案1】:
原因是因为setState
uses Object.is
equality by default 会比较旧值和新值,而tempItems === items
即使在你改变了其中的一个对象之后也是如此。
如果您将 State Hook 更新为与当前状态相同的值,React 将退出而不渲染子级或触发效果。
你可以通过改变数组的一个副本来解决这个问题:
let tempItems = [...data]; // You call it `data` here, but I assume it's the same as `items` above.
但如果有任何事情取决于 item
的变化,你会遇到同样的问题,所以你必须复制所有内容,这更昂贵:
let tempItems = data.map(d => (...d));
另一种方法是只复制您要变异的内容(或切换到不可变的数据结构库,如 Immer 或 Immutable.js):
let lastIndex = data.length - 1;
// Copy _only_ the value we're going to mutate
let tempItems = data.map((d, i) => i !== lastIndex ? d : ...d);
【讨论】:
这是正确答案。我做了一个浅拷贝,现在不用设置id
就可以工作了。【参考方案2】:
每次调用 setItems
时,如果要渲染,都必须传递一个新数组。如果你改变了同一个数组,那么 react 的相等性检查会检查事情是否发生了变化,总是会告诉数组没有改变,所以不会发生渲染。
例如:)
let a = [animation: true]
let b = a
a[0].animation = false
console.log(a === b) // This returns true
您可以改为使用map
循环遍历数组并返回一个新数组。
const clickItem = (item: CartItemType) =>
let tempItems = data.map((a, index) =>
if (index !== (item.id - 1)) return a
return ...a, animation: "item animate__animated animate__zoomOut"
)
setItems(tempItems!);
;
【讨论】:
【参考方案3】:常见错误。我一直这样做。如果基础对象引用发生更改,则认为状态已更改。 所以这些对象不会改变:
给定:
interface
id: number
type_name: string
const [items, setItems] = useState([] as CartItemType[]);
let curItems = items;
时间:
curItems.push(new CartItemType());
setItems(curItems);
预期:
状态改变被触发
实际:
未触发状态更改
现在...什么时候:
curItems.push(new CartItemType());
setItems([...curItems]);
预期:
状态改变被触发
实际:
状态改变被触发
对象也是如此。事实是,如果您更改对象的底层属性(和数组,因为数组是具有数字属性名称的对象),javascript 不会考虑对象已更改,因为对该对象的引用未更改。它与 TypeScript 或 React 无关,是通用 JS 的东西;事实上,这就是 React 用作其 Ref 对象的杠杆。如果您希望它们被视为不同,请通过破坏它来创建新对象/数组来更改引用。
【讨论】:
进入当前状态是anti-pattern in React。状态应该被视为不可变的。 @EmileBergeron 这正是我所说和所做的。 在制作浅拷贝之前,您仍在推动(变异),所以看起来它可以工作,但它仍然容易失败(反模式)。 @EmileBergeron 您可以清楚地看到,这是出于演示目的并说明他们的代码失败的原因。另请注意,这是一个钩子状态。 Sean Vieira 在临时变量中复制/破坏状态值然后由 setter 函数设置的方式就是这样做的方式。 我绝对是在谈论您的 "Now... when:" 示例,这看起来像是您在回答中建议的解决方案,但无法避免改变当前状态,即使它会触发渲染。以上是关于React State 仅在设置无用状态变量以及必要的状态变量时更新的主要内容,如果未能解决你的问题,请参考以下文章