React 功能组件中 setState 的功能语法有什么用?

IT技术 javascript reactjs
2021-05-12 22:18:31

我们正在谈论具有的功能组件 useState

让我们说

const [age, setAge] = useState(0)

现在让我们说更新时age我必须使用以前的age

React 文档提到了一种称为FUNCTIONAL UPDATES 的东西,您可以在其中传递一个函数,并且该函数的参数将是状态的先前值,例如。

setState((previousAge) => previousAge + 1)

当我可以做的时候,为什么我需要这样做

setState(previousAge + 1)

使用函数有什么好处setState

我知道在基于类的组件中,有一种叫做功能方式状态更新批处理的东西,但我在功能组件文档中找不到类似的东西。

3个回答

它们不一样,如果您的更新取决于在状态中找到的先前值,那么您应该使用函数形式。如果您在这种情况下不使用函数形式,那么您的代码有时会中断。

为什么会破裂以及何时破裂

React 功能组件只是闭包,您在闭包中拥有的状态值可能已过时 - 这意味着闭包内的值与该组件处于 React 状态的值不匹配,这可能发生在以下情况:

1-异步操作(在这个例子中点击慢添加,然后在添加按钮上多次点击,你稍后会看到当点击慢添加按钮时状态被重置为闭包内部的状态)

const App = () => {
  const [counter, setCounter] = useState(0);

  return (
    <>
      <p>counter {counter} </p>
      <button
        onClick={() => {
          setCounter(counter + 1);
        }}
      >
        immediately add
      </button>
      <button
        onClick={() => {
          setTimeout(() => setCounter(counter + 1), 1000);
        }}
      >
        Add
      </button>
    </>
  );
};

2- 当您在同一个闭包中多次调用更新函数时

const App = () => {
  const [counter, setCounter] = useState(0);

  return (
    <>
      <p>counter {counter} </p>
      <button
        onClick={() => {
          setCounter(counter + 1);
          setCounter(counter + 1);
        }}
      >
        Add twice
      </button>
   
    </>
  );
}

可能会出现问题,具体取决于您的 setter 被调用的速度/频率。

如果您使用的是从闭包中获取值的简单方法,则两次渲染之间的后续调用可能不会达到预期的效果。

一个简单的例子:

function App() {
    const [counter, setCounter] = useState(0);
    const incWithClosure = () => {
        setCounter(counter + 1);
    };
    const incWithUpdate = () => {
        setCounter(oldCounter => oldCounter + 1);
    };

    return (<>
        <button onClick={_ => { incWithClosure(); incWithClosure(); }}>
            Increment twice using incWithClosure
        </button>
        <button onClick={_ => { incWithUpdate(); incWithUpdate(); }}>
            Increment twice using incWithUpdate
        </button>
        <p>{counter}</p>
    </>);
}

两个按钮都调用了两次增量方法之一。但我们观察到:

  • 第一个按钮只会将计数器增加 1
  • 第二个按钮将使计数器增加 2,这可能是所需的结果。

这什么时候可以发生?

  • 显然,如果incWithClosure在彼此之后立即被多次调用
  • 如果涉及异步任务,这很容易发生(见下文)
  • 也许,如果 React 有很多工作要做,它的调度算法可能会决定使用相同的事件处理程序处理多次非常快速的点击

异步工作示例(模拟加载资源):

function App() {
  const [counter, setCounter] = useState(0);
  const incWithClosureDelayed = () => {
    setTimeout(() => {
      setCounter(counter + 1);
    }, 1000);
  };
  const incWithUpdateDelayed = () => {
    setTimeout(() => {
      setCounter((oldCounter) => oldCounter + 1);
    }, 1000);
  };

  return (
    <>
      <button onClick={(_) => incWithClosureDelayed()}>
        Increment slowly using incWithClosure
      </button>
      <button onClick={(_) => incWithUpdateDelayed()}>
        Increment slowly using incWithUpdate
      </button>
      <p>{counter}</p>
    </>
  );
}

单击第一个按钮两次(在一秒内)并观察计数器仅增加 1。第二个按钮具有正确的行为。

因为如果你不这样做,你发现在某个时候你会得到一个旧的值age问题是,有时你的建议会奏效。但有时不会。它今天可能不会破坏您当前的代码,但可能会破坏您几周前编写的不同代码或几个月后您当前的代码。

症状真的,真的很奇怪。您可以使用打印变量的值JSX组件内{x}的语法和以后使用打印相同的变量console.log 之后呈现JSX组件(以前没有),并发现该console.log值是陈旧-在console.log之后发生的渲染能以某种方式有旧值比渲染。

因此,状态变量的实际值在常规代码中可能并不总是正确工作——它们仅被设计为在渲染中返回最新值。出于这个原因,实现了状态设置器中的回调机制,以允许您在渲染之外的常规代码中获取状态变量的最新值。