Skip to content

React useState Hook

useState 可以声明一个状态变量,并提供了更新状态的函数

如下(如果使用ts记得设置类型):

tsx
import { useState } from 'react';

const [count, setCount] = useState<number>(0);

然后可以在组件中使用这两个

如:

tsx
import { useState } from 'react';

const MyComponent = () => {
  const [count, setCount] = useState<number>(0);

  return (
    <div>
      <p> {count} </p>
      <button onClick={() => setCount(count + 1)}>加一</button>
    </div>
  );
}

在点击 “加一” 的时候,count就会加一,触发组件重新渲染

如果这样,在set后紧接着打印count的值,会发现,打印的是更新前的值

image

tsx
<button
  onClick={() => {
    setCount(count + 1);
    console.log(count);
  }}
  >
  加一
</button>

因为 React 的异步更新机制 当设置状态后,并不会立即更新,而是在下一次渲染周期中更新

这样做可以提高性能,因为一个组件可能会有多个状态更新,React 会将它们合并,减少不必要的重新渲染

如果需要打印更新后的值,可以使用useEffect()

深入理解更新时机

先看例子,其中在一次点击的时候,对number设置了7次state,哈哈是不是很多很乱

import { useEffect, useState } from "react";

export function Restaurant() {
  const [number, setNumber] = useState(0);

  useEffect(() => {
    console.log("useEffect", number);
  }, [number]);

  return (
    <>
      <h1>{number}</h1>
      <button
        onClick={() => {
          setNumber(0);
          setNumber(5);
          setTimeout(() => {
            setNumber(() => number + 6);
            console.log(number);
          }, 0);
          setNumber(number + 1);
          setNumber((n) => n + 33);
          setNumber(42);
          setTimeout(() => {
            setNumber((n) => n + 12);
            console.log(number);
          }, 0);
        }}
      >
        增加数字
      </button>
    </>
  );
}

猜猜点击后输出结果是什么呢?54?42?又或者12?

看看ai的答复,gemini1.5、gpt4、gpt4o的回答是:

image.png

诶呦,怎么都说的一样呀,难不成偷偷私下串通好了?

然而答案是:

image.png

啊?什么意料之外的值?

看着console和代码,结合官方文档的说法,也是能够明白为什么了

我们可以知道setTimeout的是肯定不和原来的在一起执行了,所以顺序变成这样:

setNumber(0);
setNumber(5);
setNumber(number + 1);
setNumber((n) => n + 33);
setNumber(42);
setTimeout(() => {
setNumber(() => number + 6);
console.log(number);
}, 0);
setTimeout(() => {
setNumber((n) => n + 12);
console.log(number);
}, 0);

显而易见,首先成功更新的值是42

然后由于两个setTimeout都是延迟0毫秒执行,react便将他们按顺序执行了,所以当第一个setTimeout➕6后,第二个setTimeout就根据更新后的值进行➕12 (因为使用了更新函数n => n+1),最后就是0+6+12=18啦,为什么不是42起加呢,明明第一次都赋值了呀,原因是因为setTimeout在第一次中拿到的number是还没更新后的值!拿着原来的值离开,就算离开后值改变了,手里的依旧是原来那个它,所以只能根据原来的它进行计算赋值了

基于 MIT 许可发布