反应挂钩以根据先前的状态值更新状态[重复]

Posted

技术标签:

【中文标题】反应挂钩以根据先前的状态值更新状态[重复]【英文标题】:React hooks to update state based on the previous state value [duplicate] 【发布时间】:2020-10-28 08:00:34 【问题描述】:

使用钩子根据之前的状态值更新状态,我不明白为什么修改现有对象并将其传递给 setState() 是不好的。我知道这不会导致重新渲染,因为状态仍然指向相同的引用,但除此之外,问题是什么?我不明白如何克隆数组,对其进行修改,然后将其传递给 setState() 来解决一些未知问题。

const [bigArr, setBigArr] = setState(Array(SOME_BIG_NUMBER).fill(false));
// (1) This seems to work, but is bad for some reason.  But why?
bigArr[325] = true;
setBigArr(bigArr);
// (2) This is preferable for some reason. Why?
bigArrCopy = bigArr.slice();
bigArrCopy[325] = true;
setBigArr(bigArrCopy);
// (3) Is this OK?  Why/Why not?
setBigArr(bigArrCopy => 
    bigArrCopy[325] = true;
    return bigArrCopy;
);

【问题讨论】:

【参考方案1】:

React 在重新渲染之前检查是否 bigArrState !== prevBigArrState。它不检查数组的内容。它检查它是否是同一个实例。在您的第一个示例中,这将导致错误,并且组件不会重新渲染。当您使用 bigArr.slice() 时,您正在创建一个全新的数组,因此 bigArrState !== prevBigArrState 的结果为 true,从而允许重新渲染组件。

您的最后一个示例将导致问题,因为更新程序 func 没有通过 bigArrCopy 而是通过 bigArrState(相同实例)。

https://reactjs.org/docs/react-component.html#setstate

您可以执行以下操作,而不是在内存中创建和存储整个克隆:

setBigArr([
  ...bigArr.slice(0, 325),
  true,
  ...bigArr.slice(326),
]);

【讨论】:

【参考方案2】:

第一种情况不会按预期工作,它不会重新渲染组件,因为 React 使用浅比较,这意味着它会比较对象和数组的位置,如果位置不变,React 不会触发重新渲染

// (1) This will not re-render the component, cuz the location of bigArr are not changed
bigArr[325] = true;
setBigArr(bigArr);

第三种情况也一样

你可能会说你可以通过拨打setBigArr([...bigArr]);来修复它。但是还是有问题

这是一个简单的情况,你会得到意想不到的结果

import React,  useState  from "react";
import "./styles.css";

export default function App() 
  const [arr, setArr] = useState([1]);

  const handleClick = () => 
    createTimeout()

    // Case1: directly set state 
    arr[0]= 2
    setArr(arr);

    // Case1: Not directly set state 
    // const newArr = [...arr]
    // newArr[0]= 2
    // setArr(newArr);
  ;

  const createTimeout = () =>
    setTimeout(() =>
      // You expect number 1 here because you call this before update state
      console.log(arr[0])
    ,2000)
  
  return (
    <div className="App">
      <h1>arr[0]</h1>
      <div onClick=handleClick>change</div>
    </div>
  );

我们在setState 之前调用createTimeout,所以我们预计会记录号码1,但是: 案例 1:您将获得 2 号,因为您改变了原始数组案例 2:您将获得 1 号(预期)

Check out the codesandbox

【讨论】:

【参考方案3】:

我知道这不会导致重新渲染,因为状态仍然指向相同的引用,但除此之外,还有什么问题?

这还不够吗?设置状态的原因是因为您希望组件重新呈现。如果你试图让它重新渲染但它没有,那是一个非常严重的错误。

react 使用不可变状态模型的根本原因是它可以非常简单地判断状态是否改变。在两个状态之间进行快速===,您会立即知道它是否发生了变化。如果你改变你的状态,这个特性就会丢失,任何依赖它的代码都会中断。

【讨论】:

以上是关于反应挂钩以根据先前的状态值更新状态[重复]的主要内容,如果未能解决你的问题,请参考以下文章

反应JS |为啥在输入更改时更新此状态挂钩会清除输入以防止写入任何内容?

使用反应挂钩将基于类的组件转换为功能组件[重复]

更新反应状态而不覆盖先前的状态

使用 GraphQL 查询更新反应状态 [重复]

如何正确更新处于反应状态的对象或数组[重复]

React.js:在更改状态的输入字段中单击取消时如何返回先前的状态值[重复]