React 在 useEffect 中不断累加一个 state 的值
2023-04-27 11:11:49
## 问题描述
React 在 useEffect 中不断累加一个 state 的值,先看下面代码,count 值加一次,就不会加了
```js
import {useEffect, useState} from "react";
const Foo = () => {
const [count, setCount] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1)
}, 1000)
return () => clearInterval(timer);
}, [])
return <div>{count}</div>
}
```
setInterval 是一个异步操作,而 setCount 是一个更新 state 的异步操作。在 useEffect 中使用了一个空数组 `[]` 来作为 useEffect 的依赖项,表示这个 effect 不依赖任何 props 或 state 的值,所以它只会在组件渲染时执行一次,这个时候 count 的值为 0。
在 setInterval 中的回调函数中,每次调用 setCount(count+1) 时,它实际上是使用了闭包中的 count 变量,而不是最新的 count 值。所以每次执行回调时,都会使用最开始执行 useEffect 时的那个 count 值,即 0,所以每次 setCount 的结果都是将 0 加 1,最终的结果始终是 1。
## 解决方案1:使函数式更新来获取最新的值
```
setInterval(() => {
useEffect(() => {
const timer = setInterval(() => {
setCount(count => count + 1)
}, 1000)
return () => clearInterval(timer); // 组件卸载时清除定时器,避免内存泄漏
}, [])
}, 1000)
```
每次定时器执行时,都会创建一个新的闭包,将当前状态 count 的值作为参数传递函数,从而更新组件的状态。
上面用了缩写方式,与下面代码等价
```
setInterval(() => {
useEffect(() => {
setInterval(() => {
setCount(function (count) {
return count + 1
})
}, 1000)
return () => clearInterval(timer); // 组件卸载时清除定时器,避免内存泄漏
}, [])
}, 1000)
```
## 解决方案2 使用 useRef 来保存一个可变的变量
```
import { useState, useEffect, useRef } from 'react'
const Foo = () => {
const [count, setCount] = useState(0)
const countRef = useRef(0)
useEffect(() => {
setInterval(() => {
countRef.current += 1
setCount(countRef.current)
}, 1000)
}, [])
return (
<div>{count}</div>
)
}
```
使用了 useRef 来创建了一个变量 countRef,并将其初始值设置为 0。在 useEffect 中,我们将回调函数改为先修改 countRef.current 的值,然后再调用 setCount 来更新 count 的值。这样就能保证每次更新 count 的值都是基于最新的 countRef.current,而不是基于旧的 count 值。
你也可以根据业务需要,决定是否直接使用 countRef 而不需要 count。