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。