useEffect
useEffect 是一个 Hook,允许执行带有副作用的函数:
useEffect(setup, dependencies?)
setup 是一个函数,用来执行任意需要执行的代码。setup 可以返回一个 cleanup 函数,当依赖发生变化时,会首先使用旧值调用 cleanup 函数。
dependencies 是一个数组,用来传递依赖项。
若 dependencies 为 undefined,则 setup 每次渲染都会执行一次。
若 dependencies 为空数组,则 setup 只会在第一次渲染的时候执行一次。
若 dependencies 不为空,则当 dependencies 发生变化时,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 来更新值。