【问题标题】:React setter function not updating state as expectedReact setter 函数未按预期更新状态
【发布时间】:2022-01-01 14:03:59
【问题描述】:

我对 React 有点陌生,我遇到了一个问题,我希望有人愿意帮助我理解为什么我的方法不起作用。

我有这个状态:

const [beers, setBeers] = useState([
    {
      id: 8759,
      uid: "8c5f86a9-87bf-41fa-bc7f-044a9faf10be",
      brand: "Budweiser",
      name: "Westmalle Trappist Tripel",
      style: "Fruit Beer",
      hop: "Liberty",
      yeast: "1056 - American Ale",
      malts: "Special roast",
      ibu: "22 IBU",
      alcohol: "7.5%",
      blg: "7.7°Blg",
      bought: false
    },
    {
      id: 3459,
      uid: "7fa04e27-0b6b-4053-a26b-c0b1782d31c3",
      brand: "Kirin",
      name: "Hercules Double IPA",
      style: "Amber Hybrid Beer",
      hop: "Nugget",
      yeast: "2000 - Budvar Lager",
      malts: "Vienna",
      ibu: "18 IBU",
      alcohol: "9.4%",
      blg: "7.5°Blg",
      bought: true
    }]

我正在使用地图函数渲染啤酒,并且我有一些调用 handleClick 函数的 jsx

 <button onClick={() => handleClick(beer.id)}>
        {beer.bought ? "restock" : "buy"}
      </button>

这是被调用的函数:

 const handleClick = (id) => {
 
   setBeers((currentBeers) =>
      currentBeers.map((beer) => {
        if (beer.id === id) {
          beer.bought = !beer.bought;
          console.log(beer);
        }
        return beer;
      })
    );
    
  };

我想使用更新函数来更新状态,我直接在 setter 函数内部进行映射,并且由于 map 返回一个新数组,我认为一切都会正常工作,但实际上并没有。它仅适用于第一次单击按钮,然后停止更新值。

我注意到如果我使用这种方法:

  const handleClick = (id) => {
    const newbeers = beers.map((beer) => {
      if (beer.id === id) {
        beer.bought = !beer.bought;
      }
      return beer;
    });

    setBeers(newbeers);
  };

然后一切都按预期进行。

谁能帮我理解为什么我的第一种方法不起作用?

【问题讨论】:

  • 我在上面的代码中创建了一个sandbox,因为这也让我感到困扰。它似乎对我来说工作正常。你能查一下吗?
  • 我能想到您之前的代码无法正常工作的几个原因,但我必须先看看您如何呈现啤酒列表才能准确定位。但是 Stuart 提供的沙箱似乎运行良好。你做了什么不同的事情吗?
  • 嗨@Doppio 谢谢你也调查这个,这里是链接:codesandbox.io/s/quirky-elion-4ke2v?file=/src/App.js
  • 嗨@StuartNichols,我确实你的例子似乎有效,它看起来几乎和我在这里做的一样gr.pinterest.com/pin/708754060108151596。感谢您抽出宝贵时间发送沙盒链接!
  • 我仍然无法解释为什么它不起作用,但我可以确定中断的部分是 beer.bought = !beer.bought; return beer; 行。如果您将代码更改为return { ... beer, bought: !beer.bought },那么它可以工作。虽然我没有正确的解释,但我知道我们不应该更新/改变 Array.map 元素的值(所以 beer.bought = xxxxx 是不行的)

标签: reactjs react-hooks


【解决方案1】:

好的,我想我已经想通了。我的沙箱和您的沙箱之间的区别在于索引文件中包含了&lt;StrictMode&gt;。删除它可以解决问题,但不是正确的解决方案。所以我挖得更深了。

我们都错过的是,在您的代码中,您正在修改之前传入的状态对象。您应该创建一个新的啤酒对象,然后对其进行修改。所以这段代码有效(我希望):

setBeers((currentBeers) => 
  currentBeers.map((currentBeer) => { // changed beer to currentBeer
    const beer = {...currentBeer};
    if (beer.id === id) {
      beer.bought = !beer.bought;
    }
    return beer;
  )
});

我希望这会有所帮助。

【讨论】:

  • 关于此的另一件事是,我发现在开发中 StrictMode 会导致组件呈现两次。由于您的修改是一个布尔切换,我认为状态正在更新,但它更新了两次,看起来好像根本没有进行任何更改。 Here's a link。我今天学到了一些东西。
  • 谢谢斯图尔特!我今天学到了 2 件事,我一直想知道为什么我总是看到双重 console.logs,现在我知道了 :))。感谢您的帮助
【解决方案2】:

react 不会深入比较状态中的对象。由于您映射 beers 并仅更改一个属性,因此它们对于 react 是相同的,并且不会发生重新渲染。 您需要使用克隆对象设置状态。

例如:

import {cloneDeep} from 'lodash';

...

    setBeers(
      cloneDeep(currentBeers.map((beer) => {
          if (beer.id === id) {
            beer.bought = !beer.bought;
            console.log(beer);
          }
          return beer;
        })
      )
    );

【讨论】:

  • 非常感谢西蒙,您说的完全正确,我不得不克隆对象而不是直接对其进行变异。我接受了一个使用扩展运算符而不是 lodash 的答案,但你绝对是明星!再次感谢您的宝贵时间!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-12
  • 2015-06-11
  • 2018-11-27
相关资源
最近更新 更多