useEffect

useEffect 是一个 Hook,允许执行带有副作用的函数:

useEffect(setup, dependencies?)
  • setup 是一个函数,用来执行任意需要执行的代码。setup 可以返回一个 cleanup 函数,当依赖发生变化时,会首先使用旧值调用 cleanup 函数。

  • dependencies 是一个数组,用来传递依赖项。

    • 若 dependencies 为 undefined,则 setup 每次渲染都会执行一次。

    • 若 dependencies 为空数组,则 setup 只会在第一次渲染的时候执行一次。

    • 若 dependencies 不为空,则当 dependencies 发生变化时,setup 将会被执行。

  • useEffect 执行在组件渲染之后

  • 当依赖发生变化时,Effect 首先调用 cleanup 函数,然后再次调用 setup 函数。

对依赖的类型需要额外注意。React 使用 Object.is 比较依赖。当依赖为原生类型时 React 能够正确比较;但是当依赖为对象时,比较的实际上是对象的地址。这意味这只有当对象地址发生变化时才会执行 setup 函数。

多次更新同一个依赖

假设有这样一段代码:

const [age, setAge] = useState(42);

function handleClick() {
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
}

实际上,由于一次渲染中 age 的值是一样的,所有三次调用的结果和一次调用的结果相同。要解决这个问题,可以使用 updater 来更新 state:

function handleClick() {
  setAge(a => a + 1); // setAge(42 => 43)
  setAge(a => a + 1); // setAge(43 => 44)
  setAge(a => a + 1); // setAge(44 => 45)
}

根据先前的值更新 state

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(count + 1); // You want to increment the counter every second...
    }, 1000)
    return () => clearInterval(intervalId);
  }, [count]); // 🚩 ... but specifying `count` as a dependency always resets the interval.
}

当 count 发生变化时,setup 函数会被调用,然后修改 count。count 的变化导致 effect 清理 setInterval,然后重新创建一个 setInterval。每次 count 变化都会导致 Effect 清理和重新运行 setup 函数,这导致定时器被反复创建和销毁,这并不是一个好主意。

要解决这个问题,可以将 count 从依赖列表中移除,这样 setup 只会在第一次渲染的时候执行一次。然后将 setCount 改为 setCount(prev ⇒ prev+1)。这样 setInteraval 可以 updater 来更新值。

Last moify: 2022-12-04 15:11:33
Build time:2025-08-18 18:43:08
Powered By asphinx