为啥 useState 不触发重新渲染?

Posted

技术标签:

【中文标题】为啥 useState 不触发重新渲染?【英文标题】:Why is useState not triggering re-render?为什么 useState 不触发重新渲染? 【发布时间】:2019-10-09 12:25:40 【问题描述】:

我已经初始化了一个数组状态,当我更新它时,我的组件不会重新渲染。这是一个最小的概念验证:

function App() 
  const [numbers, setNumbers] = React.useState([0, 1, 2, 3]);
  console.log("rendering...");
  return (
    <div className="App">
      numbers.map(number => (
        <p>number</p>
      ))
      <input
        type="text"
        value=numbers[0].toString()
        onChange=newText => 
          let old = numbers;
          old[0] = 1;
          setNumbers(old);
        
      />
    </div>
  );

根据这段代码,似乎输入应该包含数字 0 开始,并且任何时候改变,状态也应该改变。在输入中输入“02”后,App 组件不会重新渲染。但是,如果我在 onChange 函数中添加一个 5 秒后执行的 setTimeout,则表明数字确实已更新。

关于为什么组件不更新的任何想法?

这是带有概念证明的CodeSandbox。

【问题讨论】:

【参考方案1】:

您正在调用setNumbers 并将它已经拥有的数组传递给它。你已经改变了它的一个值,但它仍然是同一个数组,我怀疑 React 没有看到任何重新渲染的理由,因为状态没有改变;新数组就是旧数组。

避免这种情况的一种简单方法是将数组分散到一个新数组中:

setNumbers([...old])

【讨论】:

很好的答案。我在 object.likes 之后进行排序,并且 setRecipes([...recipes.sort((a, b) => (b.likes.length - a.likes.length))]) 触发了重新渲染,一切都像魅力一样。谢谢。 ohhhh 所以它在内部比较引用而不是检查是否完全相等。说得通。如果我们可以决定自己重新渲染而不是让状态设置器在后台执行它会很好(也许它已经有可能我不知道) 子组件呢? 这是最好的答案。谢谢! 此解决方案对性能有影响吗?【参考方案2】:

你需要复制numbers 这样let old = [...numbers];

useState 仅在值发生更改时才更新该值,因此如果它是 44 并变为 7 它将更新。但是它如何知道数组或对象是否已更改。它是通过引用,所以当你做let old = numbers 你只是传递一个引用而不是创建一个新的

【讨论】:

【参考方案3】:

其他人已经给出了技术方案。对于任何对为什么会发生这种情况感到困惑的人,是因为 setSomething() 仅当且仅当先前状态和当前状态不同时才重新渲染组件。由于 javascript 中的数组是引用类型,所以如果在 js 中编辑数组的一项,它仍然不会改变对原始数组的引用。在 js 的眼里,这两个数组是一样的,尽管这两个数组里面的原始内容是不同的。这就是为什么 setSomething() 失败确实会检测到对旧数组所做的更改。

请注意,如果您使用类组件并使用 setState() 更新状态,那么无论状态是否已更改,组件都会始终更新。因此,您可以将功能组件更改为类组件作为解决方案。或者按照其他人提供的答案。

【讨论】:

【参考方案4】:

你可以像这样改变状态

const [state, setState] = ()
setState(...state)

或者如果你的状态是数组,你可以这样改变

const [state, setState] = ([])
setState([...state])

【讨论】:

对我不起作用 这对我有用!!你能解释一下为什么会这样吗? 它对我有用。但这是什么原因呢?【参考方案5】:
  //define state using useState hook
  const [numbers, setNumbers] = React.useState([0, 1, 2, 3]);

  //copy existing numbers in temp
  let tempNumbers = [...numbers];
  // modify/add no
  tempNumbers.push(4);
  tempNumbers[0] = 10;
  // set modified numbers
  setNumbers(tempNumbers);

【讨论】:

不要简单地发布一段代码。请尝试提供更多上下文/解释。【参考方案6】:

useState 是一个 React 钩子,它提供了在功能组件中拥有状态的功能。 通常它会在 useState 变量发生变化时通知 React 重新渲染组件。


      let old = numbers;
      old[0] = 1;
      setNumbers(old);

在上面的代码中,由于您引用的是同一个变量,它存储的是引用而不是值,因此 React 不知道最新的更改,因为引用与以前的相同。

要克服使用下面的 hack,它不会复制引用,而是深度复制(复制值)


      let old = JSON.parse(JSON.stringify(numbers));
      old[0] = 1;
      setNumbers(old);

编码愉快:)

【讨论】:

以上是关于为啥 useState 不触发重新渲染?的主要内容,如果未能解决你的问题,请参考以下文章

React hooks:为啥异步函数中的多个 useState 设置器会导致多次重新渲染?

React useState 更新不是重新渲染组件

为 useState() 钩子字符串化对象以避免重新渲染是一种好习惯吗

如何使用 useState 挂钩更新状态而不重新渲染

当 useState 改变 react-complex-tree 不重新渲染时做出反应

为啥在第一次组件渲染中未定义 useState 对象值?