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
  
>

这里为什么需要setIdsetItems 不是在做什么?

【问题讨论】:

您是否尝试在 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 仅在设置无用状态变量以及必要的状态变量时更新的主要内容,如果未能解决你的问题,请参考以下文章

state和setState分析

React中的state是啥,它是如何使用的?

React 状态在其分配的变量更改时更改

React笔记2

React笔记2

React16.8的新增特性,Hook讲解