为何setTimeout中的setState会触发两次控制台日志?
React状态更新控制台重复打印问题解析
问题描述
我在React组件中定义了number和string两个state变量,在func函数中更新它们。已知组件每次渲染都拥有独立的setTimeout,当我快速在输入框中输入文本时,预期number状态最终仅会变为1一次,尽管结果确实是1,但控制台却打印了两次,这是为什么?
export default function Mycomponent() { const [number, setnumber] = useState(0); const [string, setstring] = useState(''); function func(event) { setstring(event.target.value); setTimeout(() => { setnumber(number + 1); }, 3000); // 每次渲染都有独立的setTimeout,所以我快速输入文本时,预期number最终只会变成1一次 // 结果确实是1,但控制台打印了两次,这是为什么? } console.log(number, string); return ( <> <input type='text' onChange={(e) => { func(e) }} value={string} /> </> ) }

原因解析
1. 闭包捕获旧状态值
每次输入触发onChange时,func会被调用,同时setstring更新输入内容,触发组件重新渲染。每次渲染生成的func都是独立的,里面的setTimeout回调会捕获当前渲染周期的number值:
- 第一次输入时,组件渲染时
number为0,对应的setTimeout回调捕获到的number就是0; - 快速第二次输入时,组件再次渲染,此时第一个
setTimeout还未执行,number依然是0,新的func里的setTimeout回调捕获的number还是0。
3秒后两个回调依次执行,都会调用setnumber(0 + 1),也就是尝试把number设置为1。
2. 开发环境严格模式的渲染机制
第一次setnumber(1)执行时,number从0变为1,触发组件渲染,控制台打印出1和当前字符串;
第二次setnumber(1)执行时,虽然新值和当前number的状态(1)相同,但React在开发环境的严格模式下,会故意执行两次状态更新和渲染操作,用于检测组件中是否存在不安全的副作用。这就是你看到控制台打印两次1的原因。如果是生产环境,第二次相同值的状态更新会被React跳过,不会触发渲染,控制台只会打印一次1。
解决方法
如果希望多次setTimeout回调能基于最新的状态更新,应该使用函数式更新,让React自动传入当前最新的状态值:
setTimeout(() => { setnumber(prevNumber => prevNumber + 1); }, 3000);
这样不管有多少个延迟执行的回调,都会基于当前最新的number值来计算新状态,最终number会等于你输入的次数,而不是每次都被设置为1。
内容的提问来源于stack exchange,提问作者hossein olfat
相关产品推荐
相关产品推荐

